Role Check From Keystonemiddleware

bp Role Check from Keystonemiddleware

Role Based Access Control (RBAC) requires administration of both the roles assigned to users and the rules that determine what role can perform what action. To date, OpenStack has made role assignment fairly easy to use, but modification of policy files has been manual, decentralized, and inconsistent.

The goals:

  • Allow operator assignment of the roles to operations
  • Provide a means to report what role is required for an operation
  • Allow fine grained delegations down to individual operations

Problem Description

The authorization data associated with Keystone tokens contains a set of roles that can be used to enforce access control. This is a departure from the NIST definition of Role Based Access Control in that the roles are repeated, and scoped to the projects. A user assigned a role in one project would not grant access to a resource in another project. Thus, we call this Scoped RBAC.

The default OpenStack deployments make very little use of RBAC. The ‘admin’ role is the only role that is explicitly checked across various OpenStack project policy files. The ‘admin’ role has been unclearly scoped to both global and project scoped operations. While the policy.json files are supposed to be configuration files, and editable by the end deployers, the reality is that this is difficult, and even discouraged in the official documentation.

There are numerous challenges to updating the current policy files. Changing policy now requires redeploying configuration files for each node in the service. When applying changes to a role requires coordination between keystone and the service configuration. Certain operations require other operations in order to be successful, so if the policy fails on a downstream operation the whole operation fails. This is too high a risk for most deployment.

Implementing a dynamic RBAC policy mechanism inside OpenStack has to work within the restrictions of a distributed development model. Any approach which requires changes to every project has little to no chance of succeeding. Thus, RBAC enforcement needs to be encapsulated with Keystone and Keystonemiddleware. However, the full policy check as performed by policy.json and oslo-policy is embedded deep within the code of each project, due primarily to the need to fetch a record from a database to check attributes.

When looking at most of the policy files, they either check that the user has the admin role, or that the user has any role on the project. The matching logic in the policy.json rules are very specific to each of the projects. There is little reason to modify this part of the policy. In fact, doing so might break deployments upon update.

It would be possible to have an additional call to oslo-policy from keystonemiddleware to check only the roles. However, that leaves additional questions unsolved: how do we make the role checks easily editable, but still distributed to all of the services?

The RBAC check does not require the same information required for the scope check. While the scope check requires attributes off a resource fetched from the database, the RBAC check can be performed entirely off of information involved in the request. The basic data required is:

  • The requested URL, to include the understanding of which service or endpoint the URL implements.
  • The Data returned from the Token

Proposed Change

Overview

Perform a Role check in keystonemiddelware after the token validation that uses the role to URL Pattern mappings.

Leave the current policy.json files in place, and add an RBAC check before keystonemiddleware passes control to the service specific code. This leads to a separation of concerns: Middleware enforces the role check, source code enforces the scope check.

The following changes are required to enable the RBAC check:

  • Create an entity in the resource backend for URL Patterns that contain
    • the service name
    • the HTTP Verb of the Request
    • the URL pattern
  • Create an entity in the resource backend for linking from a role to an URL pattern.
  • Create an API for upload and modification of URL patterns, to include bulk upload per service.
  • Create an API for management of role to URL Pattern mappings.
  • Deduce some values from the Documented APIs
    • the service name
    • the HTTP verb of the request
    • the pattern that will matche the original requested URL
  • Create instances via the above API that map the above values to a role ID.
  • Perform a Role check in keystonemiddelware after the token validation that uses the role to URL Pattern mappings to ensure that the user has the mapped role. Roles will be expanded via the role inference rules.

RBAC Check Flow

For an example, I am going to use the Nova call to modify a virtual machine. A user would make an HTTP call such as

curl -H "X-Auth-Token: adb5c708a55f" \
  -H "Content-type: application/json" \
  PUT https://nova1:8774:/v2.1/2497f6/servers/83cbdc \
  -d @new_values.json

With the body of the request inside the @new_values.json file.

Middleware

Inside the web server, the WSGI application runs through a set of middleware classes until it reaches keystonemiddleware.auth_token. The auth_token class reads the [keystone_authtoken] section of the provided configuration file. A new key has been added: service which will be used to select the appropriate set of URL patterns for matching.

Here is an example .. code-block:: ini

[keystone_authtoken] username=nova project_name=service auth_type=password auth_url=http://192.0.2.3:35357 password=7c48bc8f2668001d81582506f7c83d242f62502e service=compute

Fetch RBAC Data

After the token has been validated via a call to Keystone, the middleware will fetch the RBAC specific data via python-keystoneclient which calls the API.

GET https://hostname:port/v3/access/service/compute

An example of a subset of the response data is shown below:

 {
    'service': 'compute',
    'patterns': [
       {
          verbs=["GET", "POST"],
          url_pattern="/servers/{server_id}/action",
          roles=["Member", "admin"],
          admin_project_only=False
       },
       {
           verbs=["POST"],
           url_pattern="/os-cells",
           roles=["admin"],
           admin_project_only=True
       },
       {
           verbs=["PUT"],
           url_pattern="/v2.{subversion}/{tenant_id}​/servers/​{server_id}"
           roles=["Member", "admin"],
           admin_project_only=False
       }
    ],
    'default': {
        roles=["Member", "admin"],
        admin_project_only=False
    },
}

Role check

For this example, we will specify that the expansion of role inference rules in the token response is disabled. This will minimize the token response data size as the number of defined roles increases.

keystonemiddleware.auth_token will use python-keystoneclient to make a remote query against the keystone URL pattern API passing in the parameter service to get the approprate set of rules. Due to caching needs, this result will be stored in cache so that the reposne can also be loaded directly from it’s JSON representation.

By the time the code has passed to Keystonemiddleware, the complete URL will have been processed by the WSGI pipeline, removing the Hostname and port. The remainder of the URL will most likely start with the version information in the pattern /v[0-9.]*/. In our example, this leaves: /v2.1/2497f6​/servers/​83cbdc. The pattern matching will be run against this sub-url.

keystonemiddleware will iterate through the set of patterns, attempting a match against each one. The URL remainder above will match the pattern

GET /v2.1/​{tenant_id}​/servers/​{server_id}​

Since the token response will have the role “Member” which matches the set of roles: roles=[“Member”, “admin”] the validation will succeed.

If none of the URLs match, or if the auth-data does not contain a role from the set specified by the pattern, validation fails. The failure path will be similar to a failed token validation.

After the token and RBAC validation is completed successfuly, there is no change to existing processing. The auth_token middleware adds several additional headers to the request and completes. The WSGI middleware pipeline continues, eventually calling into the Nova server specific code. Inside this code, Nova will call the oslo-policy library to enforce policy as specified by either the Nova annotations or the overloads provided in the policy.json or policy.yaml files.

Object Schema

url_pattern

ID: Autogenerated UUID Service: Indexable String, matches the values from the service catalog URL_Pattern: Long String (>255 chars) that contains the patterns. role_id: UUID index to the role table admin_project_only: Boolean

Additional Details

A catch all rule will indicate how to handle unspecified APIs. The expected rule is that APIs require the Member rule. However, once appropriate hardening has been performed, this default should be set to the admin role instead.

If an API should be reserved for cloud admin, the pattern match will have an additional Boolean field is_admin_project. If this field is set, only tokens with auth_data that includes is_admin_project=True will match.

Bulk Upload and Query of patterns

Initialization of a system requires a set of rules for each of the services. These rules should be maintained by the core team for each service, and modified by the end deployer. The value of admin_project_only is optional and will default to False.

A sample of a subset of the rules for glance could look like this:

{
'service': 'image',
'patterns':[
   {
   'url_pattern': '/v2/images',
   'verbs': ['POST'],
   'role': 'member'
   },
   {
   'url_pattern': '/v2/images/{image_id}',
   'verbs': ['GET','PATCH','DELETE'],
   'role': 'member'
   },
   {
   'url_pattern': '/v2/images/{image_id}/deactivate',
   'verbs': ['POST'],
   'role': 'member'
   },
   {
   'url_pattern': '/v2/images/{image_id}/reactivate',
   'verbs': ['POST'],
   'role': 'member',
   }
   ],
   'default': {
       roles=["Member", "admin"],
       admin_project_only=False
   },
}

Offline Role query

If a user wishes to be able to deduce what role they need to perform an operation, they can fetch the URL patterns from Keystone, and find the pattern that matches, and what role it requires. For example, if a user wanted to create a trust for a service that was going to have to check a block device, they could take the URL:

https://cinder:8776/v1/f0123/volumes/a0321

Which would match the pattern below:

{
   'service': 'storage',
   'patterns':[
   {
     'url_pattern': '/v1/{tenant_id}/volumes/{volume_id}',
     'verbs': ['GET'],
     'role': 'auditor',
   }]
 }

And show they needto delegate the auditor role. Assuming the role inference rule that states Member implies auditor, a user with the Member role can then create a trust with the implied auditor rule for the remote service.

For a Web UI like Horizon, this method could be used to customize the User interface, to determin if a class of resources should be shown, and whether or not they are editable, based on the roles of the user and the APIs needed to populate that page.

Alternatives

Many of the earlier proposals have attempted to work with the existing policy structure. Several proof-of-concepts have been written that dynamically fetch the oslo-policy, or remotely execute a comparable check.

The main reason for not pursuing this approach is that it is very hard to abstract it while continuing to provide the full set of data required. For example, project Moon (see references) was able to make a check work based on the URL only, it did not actually have the Server data from the database at middleware time. Also, the amount of administration, especially the definition of attributes, meant that the domain structure from Nova was duplicated in the Keystone Database.

The current approach to scoping policy can be described as “all resources of the same type withing a project have the same access control.” Several projects, most notably around credentials in Barbican and Keystone, have attempted to enforce more fine grained policy than the current approach, specifically, based on the user that created the object. However this has been shown to be problematic at cloud scale. Any delegations created that attempt to use those objects must now use impersonation, which is dangerous. To clean up these resources, should that user not be present is to escalate it to an administrator.

The RBAC approach described here does not prescribe such an approach, it just takes a more pragmatic and scalable approach first. This approach better matches the OpenStack design.

Other specs that have addressed this are listed in references.

If the number of implied roles increases significantly, it will be impractical to continue to expand them in the token validation bodies, as this will greatly increase the size of the response. Instead, the expansion of implied roles can happen in a modified response for list URL Patterns. The matching logic will be the same, but the token validations role list will only have specified roles.

Example:

Assuming an implied role chain like this: r1->r2->r3->r4->r5->r6->r7

And an URL pattern rule like this:

{
'url_pattern': '/v2/images/{image_id}/reactivate',
'verbs': ['POST'],
'role': 'r7',
},

The first implementation would have the (abbreviated) data in the token response:

`roles` :[{'name':'r1'},{'name':'r2'},{'name':'r3'},{'name':'r4'},
          {'name':'r5'},{'name':'r6'},{'name':'r7'}]

The latter one would have an URL pattern response that looks like this:

{
'url_pattern': '/v2/images/{image_id}/reactivate',
'verbs': ['POST'],
'roles': ['r1', 'r2', 'r3', 'r4', 'r5', 'r6', 'r7`]
},

while the token validation response would only have:

`roles` :[{'name':'r1'}]

Security Impact

The intention is that this will improve security. If checks are improperly implemented, however, it could lead to weak role checks.

One potential benefit will be the ability for users to create delegations with only the subset of roles required for the operation. For example, if a user only wants a watchdog program to kill a VM if it misbehaves, the administrator could create a role called compute_delete_server specific to the API DELETE /v2.1/​{tenant_id}​/servers/​{server_id}​ as well as a role inference rules

Here is an example .. code-block:: bnf

member -> compute_delete_server compute_delete_server -> DELETE /v2.1/​{tenant_id}​/servers/​{server_id}​

The user could then create a trust with only the role compute_delete_server specified.

Since the Nova policy file only checks that the project ID matches, and does not do any explicit role check, the nova policy file would remain unchanged.

Notifications Impact

Notifications for changes to API patterns will be comparable to the notifications for the Roles API.

Notifications due to failed token validations now will also include those that are from failed RBAC checks.

Other End User Impact

Minimal. APIs should behave just like they have before.

Performance Impact

The overall performance impact here is hard to judge. Here are some issues that have been discussed thus far.

There would be a small, but non-zero impact in the remote service due to the need to fetch and cache the RBAC data. Since the API matching rules fetched from in the Keystone server will likely be cached in the remote server, there should be minimal impact on the Keystone side due to database lookups.

Evaluating the rules would require a linear match, much the same way that a router does in Keystone. The longer the set of roles, the longer it will take to match. More complex matching schemes based on the URL patterns can potentially optimize this if it proves to be a problem.

One positive impact is that, for tokens without valid roles, code that would have, in previous cases, called into the database layer of the services will no longer have to do so. The RBAC check will go to the Keystone server prior to the object being fetched from the database.

Other Deployer Impact

Deployers will now be able to deploy their own policies for just the RBAC stage. Since this requires configuration changes to activate, no change in behavior will happen until the changes are made. It is assumed that changes would be made to the Keystone server that allow it to ignore the additional parameters passed by middleware, so that middleware can safely be upgraded.

Once the code changes are in place, the deployer will have to load the rules to the Keystone server before relying on this mechanism. They can either edit the rules before uploading them in bulk, or can use the patch verb to upload modified URL-Pattern to role mappings for a subset of the URLs.

The API will limit the URL-pattern to a single Role. The preferred mechanism for managing what the required roles for an operation is to define implied-roles that map from Admin or Member to an operation specific role. These changes can be made without modifying individual role assignments.

As an example, assume a site wants to implement a specific role for reading only operations, and to start, wants to implem,ent it for the glance image GET operation. Assuming they started with the rule above for the image`service and `url_pattern of /v2/images/{image_id}, which is initialized to the member role the deployer would do the following:

  1. Create a new role with the name reader
  2. Create a role inference rule that member implied reader
  3. Change the URL Pattern so that instead of requiring the member role it requires the reader role.

Retrieving the URL patterns would result in the following entries.

 {
    {
    'url_pattern': '/v2/images/{image_id}',
    'verbs': ['patch','delete'],
    'role': 'member'
    },
    {
    'url_pattern': '/v2/images/{image_id}',
    'verbs': ['get'],
    'role': 'reader'
    },
}

Developer Impact

The first pass of generating the new RBAC rules can be done using the API documentation, as that lists the calls in the expected format. Eventually, these documents should be managed by the individual service git repos.

Implementation

Assignee(s)

Primary assignee:
Adam Young ayoung ayoung@redhat.com
Other contributors:

Work Items

  • API for administration of API patterns
  • Logic to match the current URL to the URL pattern and perform the role check implemented in python-keystoneclient
  • Composition of the default rules.
  • Extensions to the Token Validation API to allow for new parameters
  • Modification of keystonemiddleware.auth_token to perform in process validation.
  • Modification of keystonemiddleware.auth_token to add the parameters to the validation call if activated.

Dependencies

  • No new dependencies are introduced by this change.

Documentation Impact

Documentation is required of the new APIs, the extension to the exciting API, and the rules regarding role validation.