Skip to content

Commit

Permalink
Adding RBAC policy system and checks for identity
Browse files Browse the repository at this point in the history
Adding file based RBAC engine for Horizon using copies of nova and
keystone policy.json files

Policy engine builds on top of oslo incubator policy.py, fileutils
was also pulled from oslo incubator as a dependency of policy.py

When Horizon runs and a policy check is made, a path and mapping of
services to policy files is used to load the rules into the policy
engine.  Each check is mapped to a service type and validated.  This
extra level of mapping is required because the policy.json files
may each contain a 'default' rule or unqualified (no service name
include) rule.  Additionally, maintaining separate policy.json
files per service will allow easier syncing with the service
projects.

The engine allows for compound 'and' checks at this time.  E.g.,
the way the Create User action is written, multiple APIs are
called to read data (roles, projects) and more are required to
update data (grants, user).

Other workflows e.g., Edit Project,  should have separate save
actions per step as they are unrelated.  Only the applicable
policy checks to that step were added.  The separating unrelated
steps saves will should be future work.

The underlying engine supports more rule types that are used in the
underlying policy.json files.

Policy checks were added for all actions on tables in the Identity
Panel only.  And the service policy files imported are limited in
this commit to reduce scope of the change.

Additionally, changes were made to the base action class to add
support or setting policy rules and an overridable method for
determining the policy check target. This reduces the need for
redundant code in each action policy check.

Note, the benefit Horizon has is that the underlying APIs will
correct us if we get it wrong, so if a policy file is not found for
a particular service, permission is assumed and the actual API call
to the service will fail if the action isn't authorized for that user.

Finally, adding documentation regarding policy enforcement.

Implements: blueprint rbac

Change-Id: I4a4a71163186b973229a0461b165c16936bc10e5
  • Loading branch information
dklyle committed Aug 26, 2013
1 parent 71ddcb0 commit 5984e34
Show file tree
Hide file tree
Showing 20 changed files with 1,801 additions and 3 deletions.
2 changes: 2 additions & 0 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ the following topic guides.
:maxdepth: 1

topics/tables
topics/policy
topics/testing

API Reference
Expand All @@ -94,6 +95,7 @@ In-depth documentation for Horizon and its APIs.
ref/decorators
ref/exceptions
ref/test
ref/policy

Source Code Reference
---------------------
Expand Down
140 changes: 140 additions & 0 deletions doc/source/topics/policy.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
============================================================
Horizon Policy Enforcement (RBAC: Role Based Access Control)
============================================================

Introduction
============

Horizon's policy enforcement builds on the oslo-incubator policy engine.
The basis of which is ``openstack_dashboard/openstack/common/policy.py``.
Services in OpenStack use the oslo policy engine to define policy rules
to limit access to APIs based primarily on role grants and resource
ownership.

The Keystone v3 API provides an interface for creating/reading/updating
policy files in the keystone database. However, at this time services
do not load the policy files into Keystone. Thus, the implementation in
Horizon is based on copies of policy.json files found in the service's
source code. The long-term goal is to read/utilize/update these policy
files in Horizon.

The service rules files are loaded into the policy engine to determine
access rights to actions and service APIs.

Horizon Settings
================

There are a few settings that must be in place for the Horizon policy
engine to work.

``POLICY_FILES_PATH``
---------------------

Default: ``os.path.join(ROOT_PATH, "conf")``

Specifies where service based policy files are located. These are used to
define the policy rules actions are verified against. This value must contain
the files listed in ``POLICY_FILES`` or all policy checks will pass.

.. note::

The path to deployment specific policy files can be specified in
``local_settings.py`` to override the default location.


``POLICY_FILES``
----------------

Default: { 'identity': 'keystone_policy.json', 'compute': 'nova_policy.json'}

This should essentially be the mapping of the contents of ``POLICY_FILES_PATH``
to service types. When policy.json files are added to the directory
``POLICY_FILES_PATH``, they should be included here too. Without this mapping,
there is no way to map service types with policy rules, thus two policy.json
files containing a "default" rule would be ambiguous.

.. note::

Deployment specific policy files can be specified in ``local_settings.py``
to override the default policy files. It is imperative that these policy
files match those deployed in the target OpenStack installation. Otherwise,
the displayed actions and the allowed action will not match.

``POLICY_CHECK_FUNCTION``
-------------------------

Default: ``policy.check``

This value should not be changed, although removing it would be a means to
bypass all policy checks.


How user's roles are determined
===============================

Each policy check uses information about the user stored on the request to
determine the user's roles. This information was extracted from the scoped
token received from Keystone when authenticating.

Entity ownership is also a valid role. To verify access to specific entities
like a project, the target must be specified. See the section
:ref:`rule targets <rule_targets>` later in this document.

How to Utilize RBAC
===================

The primary way to add role based access control checks to panels is in the
definition of table actions. When implementing a derived action class,
setting the :attr:`~horizon.tables.Action.policy_rules` attribute to valid
policy rules will force a policy check before the
:meth:`horizon.tables.Action.allowed` method is called on the action. These
rules are defined in the the policy files point to by ``POLICY_PATH`` and
``POLICY_FILES``. The rules are role based, where entity owner is also a
role. The format for the ``policy_rules`` is a list of two item tuples. The
first component of the tuple is the scope of the policy rule, this is the
service type. This informs the policy engine which policy file to reference.
The second component is the rule to enforce from the policy file specified by
the scope. An example tuple is::

("identity", "identity:get_user")

x tuples can be added to enforce x rules.

.. note::

If a rule specified is not found in the policy file. The policy check
will return False and the action will not be allowed.

The secondary way to add a role based check is to directly use the
:meth:`~openstack_dashboard.policy.check` method. The method takes a list
of actions, same format as the :attr:`~horizon.tables.Action.policy_rules`
attribute detailed above; the current request object; and a dictionary of
action targets. This is the method that :class:`horizon.tables.Action` class
utilizes.

.. note::

Any time multiple rules are specified in a single `policy.check` method
call, the result is the logical `and` of each rule check. So, if any
rule fails verification, the result is `False`.

.. _rule_targets:

Rule Targets
============

Some rules allow access if the user owns the entity. Policy check targets
specify particular entities to check for user ownership. The target parameter
to the :meth:`~openstack_dashboard.policy.check` method is a simple dictionary.
For instance, the target for checking access a project looks like::

{"project_id": "0905760626534a74979afd3f4a9d67f1"}

If the value matches the ``project_id`` to which the user's token is scoped,
then access is allowed.

When deriving the :class:`horizon.tables.Action` class for use in a table, if
a policy check is desired for a particular target, the implementer should
override the :meth:`horizon.tables.Action.get_policy_target` method. This
allows a programatic way to specify the target based on the current datum. The
value returned should be the target dictionary.
17 changes: 17 additions & 0 deletions doc/source/topics/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,23 @@ Default: ``20``
Similar to ``API_RESULT_LIMIT``. This setting currently only controls the
Glance image list page size. It will be removed in a future version.

``POLICY_FILES_PATH``
---------------------

Default: ``os.path.join(ROOT_PATH, "conf")``

Specifies where service based policy files are located. These are used to
define the policy rules actions are verified against.

``POLICY_FILES``
----------------

Default: { 'identity': 'keystone_policy.json', 'compute': 'nova_policy.json'}

This should essentially be the mapping of the contents of ``POLICY_FILES_PATH``
to service types. When policy.json files are added to ``POLICY_FILES_PATH``,
they should be included here too.

Django Settings (Partial)
=========================

Expand Down
27 changes: 27 additions & 0 deletions doc/source/topics/tables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -127,3 +127,30 @@ require data, such as :meth:`~horizon.tables.DataTable.get_object_display` or
actions is that you can avoid having to do all the processing, API calls, etc.
associated with loading data into the table for actions which don't require
access to that information.

Policy checks on actions
------------------------

The :attr:`~horizon.tables.Action.policy_rules` attribute, when set, will
validate access to the action using the policy rules specified. The attribute
is a list of scope/rule pairs. Where the scope is the service type defining
the rule and the rule is a rule from the corresponding service policy.json
file. The format of :attr:`horizon.tables.Action.policy_rules` looks like::

(("identity", "identity:get_user"),)

Multiple checks can be made for the same action by merely adding more tuples
to the list. The policy check will use information stored in the session
about the user and the result of
:meth:`~horizon.tables.Action.get_policy_target` (which can be overridden in
the derived action class) to determine if the user
can execute the action. If the user does not have access to the action, the
action is not added to the table.

If :attr:`~horizon.tables.Action.policy_rules` is not set, no policy checks
will be made to determine if the action should be visible and will be
displayed solely based on the result of
:meth:`~horizon.tables.Action.allowed`.

For more information on policy based Role Based Access Control see:
:doc:`Horizon Policy Enforcement (RBAC: Role Based Access Control) </topics/policy>`.
31 changes: 31 additions & 0 deletions horizon/tables/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class BaseAction(html.HTMLElement):
handles_multiple = False
requires_input = False
preempt = False
policy_rules = None

def __init__(self, datum=None):
super(BaseAction, self).__init__()
Expand All @@ -63,6 +64,14 @@ def data_type_matched(self, datum):
return False
return True

def get_policy_target(self, request, datum):
""" Provide the target for a policy request.
This method is meant to be overridden to return target details when
one of the policy checks requires them. E.g., {"user_id": datum.id}
"""
return {}

def allowed(self, request, datum):
""" Determine whether this action is allowed for the current request.
Expand All @@ -71,6 +80,12 @@ def allowed(self, request, datum):
return True

def _allowed(self, request, datum):
policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None)

if policy_check and self.policy_rules:
target = self.get_policy_target(request, datum)
return (policy_check(self.policy_rules, request, target) and
self.allowed(request, datum))
return self.allowed(request, datum)

def update(self, request, datum):
Expand Down Expand Up @@ -156,6 +171,22 @@ class Action(BaseAction):
Default to be an empty list (``[]``). When set to empty, the action
will accept any kind of data.
.. attribute:: policy_rules
list of scope and rule tuples to do policy checks on, the
composition of which is (scope, rule)
scope: service type managing the policy for action
rule: string representing the action to be checked
for a policy that requires a single rule check:
policy_rules should look like
"(("compute", "compute:create_instance"),)"
for a policy that requires multiple rule checks:
rules should look like
"(("identity", "identity:list_users"),
("identity", "identity:list_roles"))"
At least one of the following methods must be defined:
.. method:: single(self, data_table, request, object_id)
Expand Down
2 changes: 2 additions & 0 deletions openstack-common.conf
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
[DEFAULT]
module=config
module=eventlet_backdoor
module=fileutils
module=install_venv_common
module=notifier
module=policy
module=rpc
module=service
module=threadgroup
Expand Down
90 changes: 90 additions & 0 deletions openstack_dashboard/conf/keystone_policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
{
"admin_required": [["role:admin"], ["is_admin:1"]],
"service_role": [["role:service"]],
"service_or_admin": [["rule:admin_required"], ["rule:service_role"]],
"owner" : [["user_id:%(user_id)s"]],
"admin_or_owner": [["rule:admin_required"], ["rule:owner"]],

"default": [["rule:admin_required"]],

"identity:get_service": [["rule:admin_required"]],
"identity:list_services": [["rule:admin_required"]],
"identity:create_service": [["rule:admin_required"]],
"identity:update_service": [["rule:admin_required"]],
"identity:delete_service": [["rule:admin_required"]],

"identity:get_endpoint": [["rule:admin_required"]],
"identity:list_endpoints": [["rule:admin_required"]],
"identity:create_endpoint": [["rule:admin_required"]],
"identity:update_endpoint": [["rule:admin_required"]],
"identity:delete_endpoint": [["rule:admin_required"]],

"identity:get_domain": [["rule:admin_required"]],
"identity:list_domains": [["rule:admin_required"]],
"identity:create_domain": [["rule:admin_required"]],
"identity:update_domain": [["rule:admin_required"]],
"identity:delete_domain": [["rule:admin_required"]],

"identity:get_project": [["rule:admin_required"]],
"identity:list_projects": [["rule:admin_required"]],
"identity:list_user_projects": [["rule:admin_or_owner"]],
"identity:create_project": [["rule:admin_required"]],
"identity:update_project": [["rule:admin_required"]],
"identity:delete_project": [["rule:admin_required"]],

"identity:get_user": [["rule:admin_required"]],
"identity:list_users": [["rule:admin_required"]],
"identity:create_user": [["rule:admin_required"]],
"identity:update_user": [["rule:admin_or_owner"]],
"identity:delete_user": [["rule:admin_required"]],

"identity:get_group": [["rule:admin_required"]],
"identity:list_groups": [["rule:admin_required"]],
"identity:list_groups_for_user": [["rule:admin_or_owner"]],
"identity:create_group": [["rule:admin_required"]],
"identity:update_group": [["rule:admin_required"]],
"identity:delete_group": [["rule:admin_required"]],
"identity:list_users_in_group": [["rule:admin_required"]],
"identity:remove_user_from_group": [["rule:admin_required"]],
"identity:check_user_in_group": [["rule:admin_required"]],
"identity:add_user_to_group": [["rule:admin_required"]],

"identity:get_credential": [["rule:admin_required"]],
"identity:list_credentials": [["rule:admin_required"]],
"identity:create_credential": [["rule:admin_required"]],
"identity:update_credential": [["rule:admin_required"]],
"identity:delete_credential": [["rule:admin_required"]],

"identity:get_role": [["rule:admin_required"]],
"identity:list_roles": [["rule:admin_required"]],
"identity:create_role": [["rule:admin_required"]],
"identity:update_role": [["rule:admin_required"]],
"identity:delete_role": [["rule:admin_required"]],

"identity:check_grant": [["rule:admin_required"]],
"identity:list_grants": [["rule:admin_required"]],
"identity:create_grant": [["rule:admin_required"]],
"identity:revoke_grant": [["rule:admin_required"]],

"identity:list_role_assignments": [["rule:admin_required"]],

"identity:get_policy": [["rule:admin_required"]],
"identity:list_policies": [["rule:admin_required"]],
"identity:create_policy": [["rule:admin_required"]],
"identity:update_policy": [["rule:admin_required"]],
"identity:delete_policy": [["rule:admin_required"]],

"identity:check_token": [["rule:admin_required"]],
"identity:validate_token": [["rule:service_or_admin"]],
"identity:validate_token_head": [["rule:service_or_admin"]],
"identity:revocation_list": [["rule:service_or_admin"]],
"identity:revoke_token": [["rule:admin_or_owner"]],

"identity:create_trust": [["user_id:%(trust.trustor_user_id)s"]],
"identity:get_trust": [["rule:admin_or_owner"]],
"identity:list_trusts": [["@"]],
"identity:list_roles_for_trust": [["@"]],
"identity:check_role_for_trust": [["@"]],
"identity:get_role_for_trust": [["@"]],
"identity:delete_trust": [["@"]]
}
Loading

0 comments on commit 5984e34

Please sign in to comment.