horizon/openstack_dashboard/policy.py
David Lyle 5984e34862 Adding RBAC policy system and checks for identity
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
2013-08-26 10:32:28 -06:00

121 lines
4.2 KiB
Python

# vim: tabstop=4 shiftwidth=4 softtabstop=4
# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Policy engine for Horizon"""
import logging
import os.path
from django.conf import settings # noqa
from oslo.config import cfg
from openstack_auth import utils as auth_utils
from openstack_dashboard.openstack.common import policy
LOG = logging.getLogger(__name__)
CONF = cfg.CONF
_ENFORCER = None
_BASE_PATH = getattr(settings, 'POLICY_FILES_PATH', '')
def _get_enforcer():
global _ENFORCER
if not _ENFORCER:
_ENFORCER = {}
policy_files = getattr(settings, 'POLICY_FILES', {})
for service in policy_files.keys():
enforcer = policy.Enforcer()
enforcer.policy_path = os.path.join(_BASE_PATH,
policy_files[service])
if os.path.isfile(enforcer.policy_path):
LOG.debug("adding enforcer for service: %s" % service)
_ENFORCER[service] = enforcer
else:
LOG.warn("policy file for service: %s not found at %s" %
(service, enforcer.policy_path))
return _ENFORCER
def reset():
global _ENFORCER
_ENFORCER = None
def check(actions, request, target={}):
"""
Check if the user has permission to the action according
to policy setting.
:param actions: list of scope and action to do policy checks on, the
composition of which is (scope, action)
scope: service type managing the policy for action
action: string representing the action to be checked
this should be colon separated for clarity.
i.e. compute:create_instance
compute:attach_volume
volume:attach_volume
for a policy action that requires a single action:
actions should look like "(("compute", "compute:create_instance"),)"
for a multiple action check:
actions should look like "(("identity", "identity:list_users"),
("identity", "identity:list_roles"))"
:param request: django http request object. If not specified, credentials
must be passed.
:param target: dictionary representing the object of the action
for object creation this should be a dictionary
representing the location of the object e.g.
{'tenant_id': object.tenant_id}
:returns: boolean if the user has permission or not for the actions.
"""
user = auth_utils.get_user(request)
credentials = _user_to_credentials(request, user)
enforcer = _get_enforcer()
for action in actions:
scope, action = action[0], action[1]
if scope in enforcer:
# if any check fails return failure
if not enforcer[scope].enforce(action, target, credentials):
return False
# if no policy for scope, allow action, underlying API will
# ultimately block the action if not permitted, treat as though
# allowed
return True
def _user_to_credentials(request, user):
if not hasattr(user, "_credentials"):
roles = [role['name'] for role in user.roles]
user._credentials = {'user_id': user.id,
'token': user.token,
'username': user.username,
'project_id': user.project_id,
'project_name': user.project_name,
'domain_id': user.user_domain_id,
'is_admin': user.is_superuser,
'roles': roles}
return user._credentials