Pass Targets to Glance’s Policy Enforcer

https://blueprints.launchpad.net/glance/+spec/pass-targets-to-policy-enforcer

Currently it’s possible to define custom rules in Glance’s policy.json that rely on attributes other than a user’s roles. Unfortunately, if you attempt to apply one of those rules, it will always cause the user to be prevented from performing the associated action. This specification proposes that we pass the proper target objects to the enforcer so these rules can be used and properly enforced.

Problem description

Currently, Glance promises that permissions can be configured in the policy.json file but any rule other than a role check currently results in a 403 Forbidden response. As this is a promised feature, implementing this specification is merely fixing the already promised behaviour.

It is not possible to restrict access to actions in Glance’s policy.json based on anything other than a user’s roles. The policy enforcer that Glance extends from oslo expects a dictionary-like object to be passed as a target of the action. Every method that uses the policy enforcer and enforces the policy defined for the corresponding action currently passes an empty dictionary ({}) which provides absolutely no data about the actual target.

If we define a rule similar to the following:

"tenant_is_owner": "tenant%(owner)s"

And we apply it to an action, e.g.,

"delete_image": "rule:tenant_is_owner"

Then every request to delete any image will be denied with a 403 (Unauthorized) response from Glance’s API. The reason stems from how the rule is parsed and the target is passed in. The "rule:tenant_is_owner" rule will be parsed as a GenericCheck. These checks are split on the : into a kind and a match (roughly, "<kind>:<match>"). The match portion is then interpolated with the target, i.e.,

match = self.match % target

So using our example above, we would do

match = "%(owner)s" % {}

Except that this raises a KeyError which means the check immediately returns False and fails. In this particular instance (deleting an image), if we passed an instance of glance.api.policy.ImageTarget, then what would instead happen is that the interpolation would succeed.

Proposed change

The solution for image-based resources is simple. We have the image on the policy proxies that relate to images. We simply pass that to glance.api.policy.ImageTarget and pass the resulting instance to the policy enforcer so it can be accessed like a dictionary when interpolated. For members and tasks, there is no target class that we can use. These are very thin classes that could easily be written.

Once we have the appropriately defined target classes, we would then update the places where the policy is enforced to use instances of those target classes.

With proper targets in place, we can also implement safer default policy rules for operators who rely solely on the default policy.json file.

Alternatives

Custom rule creation based on attributes of the target object could be disabled. This would severely limit an operator’s ability to restrict actions based on a user’s tenant and other properties of the target of the action.

Data model impact

None

REST API impact

None

Security impact

This will give operators a significant amount of control over the security of their Glance installations. Currently they can only restrict actions based on roles which may be sufficient in some cases. If the operator, however, wishes to restrict access based on other factors (besides role) they cannot do this. If they try to do it, there is no indication that it will not work but they can essentially produce a Denial of Service to users who should be able to perform actions based on the policy defined.

Notifications impact

None

Other end user impact

None

Performance Impact

None

Other deployer impact

To leverage the fixes in this specification, operators need to update their versions of policy.json used in their deployments. To write a rule, operators need to know that there are three values provided by glance that can be used in a rule on the left side of the colon (:). Those values are the current user’s credentials in the form of:

  • role

  • tenant

  • owner

The left side of the colon can also contain any value that Python can understand, e.g.,:

  • True

  • False

  • "a string"

  • &c.

Role checks are going to continue to work exactly as they already do. If the role defined in the check is one that the user holds, then that will pass, e.g., role:admin.

Using tenant and owner will only work with Images or actions that interact with an image. Consider the following rule:

tenant:%(owner)s

This will use the tenant value of the currently authenticated user. It will also use owner from the image it is acting upon. If those two values are equivalent the check will pass. All attributes on an image (as well as extra image properties) are available for use on the right side of the colon. The most useful are the following:

  • owner

  • protected

  • is_public

An operator, therefore, could construct a set of rules like the following:

{
    "not_protected": "False:%(protected)s",
    "is_owner": "tenant:%(owner)s",
    "not_protected_and_is_owner": "rule:not_protected and rule:is_owner",
    "delete_image": "rule:not_protected_and_is_owner"
}

Developer impact

None

Implementation

Assignee(s)

Primary assignee:

icordasc

Reviewers

Core reviewer(s):

nikhil-komawar jokke

Other reviewer(s):

kragniz

Work Items

  • Create appropriate target classes

  • Use target classes to proxy targets to the policy enforcer

  • Add tests demonstrating that generic checks now work

  • Add better documentation surrounding policy rules to the existing documentation

Dependencies

None

Testing

Functional tests will be added where specific policy.json files are loaded to test access control of different targets.

Documentation Impact

There is no direct impact, but the existing policy.json documentation is thin and only describes what each rule controls. It does not describe the available target information or how to write rules.