cinder/doc/source/configuration/block-storage/policy-config-HOWTO.rst
Brian Rosmaita e0335de8b9 Better workaround for policy file in PDF docs
Follow up to cinder change Ic752048b3a1f87 with a more elegant
solution.

Credit: Elod Illes <elod.illes@est.tech> on manila change
I4f22fe7b453940.

Change-Id: I58cf850543c862c57d51b225fa928f3000accda8
2021-09-14 17:04:59 -04:00

21 KiB

Policy configuration HowTo

You can use Cinder policies to control how your users and administrators interact with the Block Storage Service. In this HowTo, we'll discuss the user model Cinder employs and how it can be modified by adjusting policies.

  • Like most OpenStack services, Cinder uses the OpenStack oslo.policy library as a base for its policy-related code. For a discussion of "rules" and "roles", other vocabulary, and general information about OpenStack policies and the policy configuration file, see Administering Applications that use oslo.policy.
  • See policy for the list of policy targets recognized by Cinder.
  • Since the Queens release, the default way to run Cinder is without a policy file. This is because sensible default values are defined in the code. To run Cinder with a custom policy configuration, however, you'll need to write your changes into a policy file.

html

  • Elsewhere in this documentation, you can find a copy of the sample policy file <./samples/policy.yaml> that contains all the default settings.
  • Instructions for generating a sample policy.yaml file directly from the Cinder source code can be found in the file README-policy.generate.md in the etc/cinder directory in the Cinder source code repository (or its github mirror).
  • OpenStack has deprecated the use of a JSON policy file since the Wallaby release (Cinder 18.0.0). If you are still using the JSON format, there is a oslopolicy-convert-json-to-yaml__ tool that will migrate your existing JSON-formatted policy file to YAML in a backward-compatible way.

Vocabulary Note

We need to clarify some terms we'll be using below.

Project

This is an administrative grouping of users into a unit that can own cloud resources. (This is what used to be called a "tenant".)

Service

This is an OpenStack component that users interact with through an API it provides. For example, "Cinder" is the OpenStack code name for the service that provides the Block Storage API versions 2 and 3. Cinder is also known as the OpenStack Block Storage Service.

The point of making this distinction is that there's another use of the term 'project' that is relevant to the discussion, but that we're not going to use. Each OpenStack service is produced and maintained by a "project team". We will not be using the term 'project' in that sense in this document. We'll always use the term 'service'. (If you are new to OpenStack, this won't be a problem. But if you're discussing this content with someone who's been around OpenStack for a while, you'll want to be clear about this so that you're not talking past each other.)

The User Model

The Cinder code is written with the expectation that there are two kinds of users.

End users

These are users who consume resources and (possibly) pay the bills. End users are restricted to acting within a specific project and cannot perform operations on resources that are not owned by the project(s) they are in.

Administrative users ("admins")

These are users who keep the lights on. They have the ability to view all resources controlled by Cinder and can perform most operations on them. They also have access to other operations (for example, setting quotas) that cannot be performed by end users.

Additionally, admins can view resource properties that cannot be seen by end users (for example, the migration status of a volume). The technical term to describe this is that when a volume-show call is made in an administrative context it will contain additional properties than when the call is not made in an administrative context. Similarly, when a volume-list call is made in an administrative context, the response may include volumes that are not owned by the project of the person making the call; this never happens when a call is not made in an administrative context.

Policies

Broadly speaking, an operator can accomplish two things with policies:

  1. The policy file can define the criteria for what users are granted the privilege to act in an administrative context.
  2. The policy file can specify for specific actions (or policy targets), which users can perform those actions.

In general, while an operator can define who can make calls in an administrative context, an operator cannot affect what can be done in an administrative context (because that's already been decided when the code was implemented). For example, the boundaries between projects are strictly enforced in Cinder, and only an admin can view resources across projects. There is no way to grant a user the ability to "see" into another project (at least not by policy configuration--this could be done by using the Identity Service to add the user to the other project, but note that at that point, the user is no longer not a member of the project owning the now visible resources.)

Pre-Defined Policy Rules

The default Cinder policy file contains three rules that are used as the basis of policy file configuration.

"context_is_admin"

This defines the administrative context in Cinder. You'll notice that it's defined once at the beginning of the sample policy file and isn't referred to anywhere else in that file. To understand what this does, it's helpful to know something about the API implementation.

A user's API request must be accompanied by an authentication token from the Identity Service. (If you are using client software, for example, the python-cinderclient or python-openstack client, the token is being requested for you under the hood.) The Block Storage API confirms that the token is unexpired and obtains other information about the requestor, for example, what roles the Identity Service recognizes the user to have. Cinder uses this information to create an internal context object that will be passed around the code as various functions and services are called to satisfy the user's request.

When the request context object is created, Cinder uses the "context_is_admin" rule to decide whether this context object will be recognized as providing an administrative context. It does this by setting the "is_admin" property to True on the context object. Cinder code later in the call chain simply checks whether the "is_admin" property is true on the context object to determine whether the call is taking place in an administrative context. Similarly, policies will refer to "is_admin:True" (either directly or indirectly) to require an administrative context.

All of this is a long-winded way to say that in a Cinder policy file, you'll only see "context_is_admin" at the top; after that, you'll see "is_admin:True" whenever you want to refer to an administrative context.

"admin_or_owner"

This is the default rule for most non-admin API calls. As the name indicates, it allows an administrator or an owner to make the call.

"admin_api"

This is the default rule for API calls that only administrators should be allowed to make.

Note

For some API calls, there are checks way down in the code to ensure that a call is being made in an administrative context before the request is allowed to succeed. Thus it is not always the case that simply changing a policy target whose value is "rule:admin_api" to "rule:admin_or_owner" (or "rule:admin_api or role:some-special-role") will give a non-admin user the ability to successfully make the call. Unfortunately, you can't tell which calls these are without experimenting with a policy file (or looking at the source code). A good rule of thumb, however, is that API calls governed by policies marked as "rule:admin_api" in the default policy configuration fall into this category.

Example: Configuring a Read-Only Administrator

A fairly common configuration request is to create a special category of administrator who has only an observer ("look but don't touch") function. The idea is that for security and stability reasons, it's a good idea to allow all users, including administrators, the least amount of privileges they need to successfully perform their job. Someone whose job is to audit information about Cinder (for example, to see what the current quota settings are) doesn't need the ability to change these settings. In this section, we'll discuss one way to configure the Cinder policy file to accomplish this.

Note

To keep the discussion focused, this example assumes that you're working from the default policy file. Hopefully the general strategy will be clear enough to be applied to clouds already using non-default configurations. Additionally, there are other logically equivalent ways to configure the policy file to introduce a read-only administrator; this is not by any means the only way to do it.

Given the job requirements, the observer administrator (who we'll refer to as the "observer-admin" for short) needs to operate in the administrative context. Thus, we'll have to adjust the "context_is_admin" definition in the policy file to include such a person. Note that this will make such a person a full administrator if we make no other changes to the policy file. Thus the strategy we'll use is to first make the observer-admin a full administrator, and then block the observer-admin's access to those API calls that aren't read-only.

Warning

Metaphorically, what we are doing is opening the floodgates and then plugging up the holes one by one. That sounds alarming, and it should. We cannot emphasize strongly enough that any policy file changes should be well-contained (that is, you know exactly who has the new role or roles) and tested (you should have some kind of tests in place to determine that your changes have only the effects you intend).

This is probably as good a place as any to remind you that the suggestions that follow are provided without warranty of any kind, either expressed or implied. Like the OpenStack source code, they are covered by the Apache License, version 2.0. In particular, we direct your attention to sections 7-9.

Step 0: Testing

We mention testing first (even though you haven't made any changes yet) because if we wait to mention it until after we've made the configuration changes, you might get the impression that it's the last thing to do (or the least important). It will make your life much easier if you come up with a plan for how you will test these changes before you start modifiying the policy configuration.

We advise setting up automated tests because the Block Storage API has a lot of API calls and you'll want to test each of them against an admin user, an observer-admin user, and a "regular" end user. Further, if you anticipate that you may require finer-grained access than outlined in this example (for example, you would like a "creator" role that can create and read, but not delete), your configuration will be all the more complex and hence require more extensive testing that you won't want to do by hand.

Step 1: Create a new role

In the Identity Service, create a new role. It's a good idea to make this a new, never before assigned role so that you can easily track who it's been assigned to. As you recall from the discussion above, this person will have full administrative powers for any functions that are missed when we do the "block up the holes" stage.

For this example, we'll use a role named cinder:reader-admin. There is nothing special about this role name; you may use any name that makes sense to the administrators who will be assigning the role and configuring the policies. (The 'cinder:' part is to remind you that this role applies to the Block Storage Service, the 'reader' part is from the role name that OpenStack has converged upon for this type of observer role, and the '-admin' part is to remind you that whoever has this role will be able to observe admin-type stuff.)

Note

Beginning with the Rocky release, the Identity Service (Keystone) creates three roles when the service is initiated: member, reader, and admin. By default, the reader role is not assigned to any users. Work is underway during the Stein cycle so that the Identity API will recognize users with the reader role as having read-only access to the Identity API. See the Keystone spec Basic Default Roles for more information.

We mention this so that you are aware that if you use a role named reader when doing the policy configuration described in this document, at some point users assigned the reader role may have read-only access to services other than the Block Storage Service. The desirability of this outcome depends upon your particular use case.

Step 2: Open the floodgates

If your installation doesn't have an /etc/cinder/policy.yaml file, you can generate one from the source code (see the introductory section of this document).

Note

The default file is completely commented out. For any of the changes you make below to be effective, don't forget to uncomment the line in which they occur.

To extend the administrative context to include the new role, change:

"context_is_admin": "role:admin"

to:

"context_is_admin": "role:admin or role:cinder:reader-admin"

Step 3: Plug the holes in the Admin API

Now we make adjustments to the policy configuration so that the observer-admin will in fact have only read-only access to Cinder resources.

3A: New Policy Rule

First, we create a new policy rule for Admin API access that specifically excludes the new role. Find the line in the policy file that has "admin_api" on the left hand side. Immediately after it, introduce a new rule:

"strict_admin_api": "not role:cinder:reader-admin and rule:admin_api"

3B: Plugging Holes

Now, plug up the holes we've opened in the Admin API by using this new rule. Find each of the lines in the remainder of the policy file that look like:

"target": "rule:admin_api"

and for each line, decide whether the observer-admin needs access to this action or not. For example, the target "volume_extension:services:index" specifies a read-only action, so it's appropriate for the observer-admin to perform. We'll leave that one in its default configuration of:

"volume_extension:services:index": "rule:admin_api"

On the other hand, if the target is something that allows modification, we most likely don't want to allow the observer-admin to perform it. For such actions we need to use the "strict" form of the admin rule. For example, consider the action "volume_extension:quotas:delete". To exclude the observer-admin from performing it, change the default setting of:

"volume_extension:quotas:delete": "rule:admin_api"

to:

"volume_extension:quotas:delete": "rule:strict_admin_api"

Do this on a case-by-case basis for the other policy targets that by default are governed by the rule:admin_api.

3C: Other Changes

You've probably figured this out already, but there may be some other changes that are implied by, but not explicitly mentioned in, the above instructions. For example, you'll find the following policies in the sample file:

"volume_extension:volume_type_encryption": "rule:admin_api"
"volume_extension:volume_type_encryption:create": "rule:volume_extension:volume_type_encryption"
"volume_extension:volume_type_encryption:get": "rule:volume_extension:volume_type_encryption"
"volume_extension:volume_type_encryption:update": "rule:volume_extension:volume_type_encryption"
"volume_extension:volume_type_encryption:delete": "rule:volume_extension:volume_type_encryption"

The first policy covers all of create/read/update/delete (and is deprecated for removal during the Stein development cycle). However, if you set it to "rule:strict_admin_api", the observer-admin won't be able to read the volume type encryption. So it should be left at "rule:admin_api" and the create/update/delete policies should be changed to "rule:strict_admin_api". Additionally, in preparation for the deprecated policy target's removal, it's a good idea to change the value of the get policy to "rule:admin_api".

Step 4: Plug the holes in the "Regular" API

As stated earlier, a user with the role cinder:reader-admin is elevated to full administrative powers. That implies that such a user can perform administrative functions on end-user resources. Hence, we have another set of holes to plug up.

4A: New Policy Rule

As we did for the Admin API, we'll create a strict version of the "admin_or_owner" rule so we can specifically exclude the observer-admin from executing that action. Find the line in the policy file where "admin_or_owner" appears on the left hand side. It probably looks something like this:

"admin_or_owner": "is_admin:True or (role:admin and is_admin_project:True) or project_id:%(project_id)s"

Immediately following it, introduce a new rule:

"strict_admin_or_owner": "(not role:cinder:reader-admin and (is_admin:True or (role:admin and is_admin_project:True))) or project_id:%(project_id)s"

Note

To understand what this change does, note that the "admin_or_owner" rule definition has the general structure:

<admin-stuff> or <project-stuff>

To construct the strict version, we need to make sure that the not cinder:reader-admin part applies only the left-hand side (the <admin-stuff>). The easiest way to do that is to structure the new rule as follows:

(not role:cinder:reader-admin and (<admin-stuff>)) or <project-stuff>

Note

If you don't need a user with the role cinder:reader-admin to manage resources in their own project, you could simplify this rule to:

"strict_admin_or_owner": "not role:cinder:reader-admin and rule:admin_or_owner"

4B: Plugging Holes

Find each line in the policy file that looks like:

"target": "rule:admin_or_owner"

and decide whether it represents an action that the observer-admin needs to perform. For those actions you don't want the observer-admin to do, change the policy to:

"target": "rule:strict_admin_or_owner"

4C: Unrestricted Policies

There are some policies in the default file that look like this:

"target": ""

These are called unrestricted policies because the requirements are empty, and hence can be satisfied by any authenticated user. (Recall from the earlier discussion of cinder-user-model, however, that this does not mean that any user can see any other user's resources.)

Unrestricted policies may be found on GET calls that don't have a particular resource to refer to (for example, the call to get all volumes) or a POST call that creates a completely new resource (for example, the call to create a volume). You don't see them much in the Cinder policy file because the code implementing the Block Storage API v2 and v3 always make sure there's a target object containing at least the project_id and user_id that can be used in evaluating whether the policy should allow the action or not.

Thus, obvious read-only targets (for example, volume_extension:type_get) can be left unrestricted. Policy targets that are not read only (for example, volume:accept_transfer), can be changed to rule:strict_admin_or_owner.

Step 5: Testing

We emphasized above that because of the nature of this change, it is extremely important to test it carefully. One thing to watch out for: because we're using a clause like not role:cinder:reader-admin, a typographical error in the role name will cause problems. (For example, if you enter it into the file as not role:cinder_reader-admin, it won't exclude the user we're worried about, who has the role cinder:reader-admin.)

As mentioned earlier, we advise setting up automated tests so that you can prevent regressions if you have to modify your policy files at some point.