Add build_target arguement to enforcer

This change adds in a new arguement "build_target" which takes
in a passed function to build the enforement target after
the authentication check. This is to avoid leaking existance
data when determining scope.

Change-Id: I9aab71dd0032d40aa2f2e088b529af08b112671f
Partial-Bug: #1776504
This commit is contained in:
Gage Hugo 2018-09-11 17:26:24 -06:00
parent 50b2d6aa0e
commit 8697da7da6
7 changed files with 140 additions and 55 deletions

View File

@ -14,6 +14,7 @@
import flask
import flask_restful
import functools
from six.moves import http_client
from keystone.common import json_home
@ -342,7 +343,7 @@ class DomainUserListResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:list_grants',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
refs = PROVIDERS.assignment_api.list_grants(
domain_id=domain_id, user_id=user_id,
inherited_to_projects=False)
@ -361,7 +362,7 @@ class DomainUserResource(ks_flask.ResourceBase):
"""
ENFORCER.enforce_call(
action='identity:check_grant',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.get_grant(
role_id, domain_id=domain_id, user_id=user_id,
inherited_to_projects=False)
@ -374,7 +375,7 @@ class DomainUserResource(ks_flask.ResourceBase):
"""
ENFORCER.enforce_call(
action='identity:create_grant',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.create_grant(
role_id, domain_id=domain_id, user_id=user_id,
inherited_to_projects=False, initiator=self.audit_initiator)
@ -387,7 +388,8 @@ class DomainUserResource(ks_flask.ResourceBase):
"""
ENFORCER.enforce_call(
action='identity:revoke_grant',
target_attr=_build_enforcement_target(allow_non_existing=True))
build_target=functools.partial(_build_enforcement_target,
allow_non_existing=True))
PROVIDERS.assignment_api.delete_grant(
role_id, domain_id=domain_id, user_id=user_id,
inherited_to_projects=False, initiator=self.audit_initiator)
@ -402,7 +404,7 @@ class DomainGroupListResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:list_grants',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
refs = PROVIDERS.assignment_api.list_grants(
domain_id=domain_id, group_id=group_id,
inherited_to_projects=False)
@ -421,7 +423,7 @@ class DomainGroupResource(ks_flask.ResourceBase):
"""
ENFORCER.enforce_call(
action='identity:check_grant',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.get_grant(
role_id, domain_id=domain_id, group_id=group_id,
inherited_to_projects=False)
@ -434,7 +436,7 @@ class DomainGroupResource(ks_flask.ResourceBase):
"""
ENFORCER.enforce_call(
action='identity:create_grant',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.create_grant(
role_id, domain_id=domain_id, group_id=group_id,
inherited_to_projects=False, initiator=self.audit_initiator)
@ -447,7 +449,8 @@ class DomainGroupResource(ks_flask.ResourceBase):
"""
ENFORCER.enforce_call(
action='identity:revoke_grant',
target_attr=_build_enforcement_target(allow_non_existing=True))
build_target=functools.partial(_build_enforcement_target,
allow_non_existing=True))
PROVIDERS.assignment_api.delete_grant(
role_id, domain_id=domain_id, group_id=group_id,
inherited_to_projects=False, initiator=self.audit_initiator)

View File

@ -13,6 +13,7 @@
# This file handles all flask-restful resources for /v3/groups
import flask_restful
import functools
from six.moves import http_client
from keystone.common import json_home
@ -147,7 +148,8 @@ class UserGroupCRUDResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:check_user_in_group',
target_attr=self._build_enforcement_target_attr(user_id, group_id))
build_target=functools.partial(self._build_enforcement_target_attr,
user_id, group_id))
PROVIDERS.identity_api.check_user_in_group(user_id, group_id)
return None, http_client.NO_CONTENT
@ -158,7 +160,8 @@ class UserGroupCRUDResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:add_user_to_group',
target_attr=self._build_enforcement_target_attr(user_id, group_id))
build_target=functools.partial(self._build_enforcement_target_attr,
user_id, group_id))
PROVIDERS.identity_api.add_user_to_group(
user_id, group_id, initiator=ks_flask.build_audit_initiator())
return None, http_client.NO_CONTENT
@ -170,7 +173,8 @@ class UserGroupCRUDResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:remove_user_from_group',
target_attr=self._build_enforcement_target_attr(user_id, group_id))
build_target=functools.partial(self._build_enforcement_target_attr,
user_id, group_id))
PROVIDERS.identity_api.remove_user_from_group(
user_id, group_id, initiator=ks_flask.build_audit_initiator())
return None, http_client.NO_CONTENT

View File

@ -13,6 +13,7 @@
# This file handles all flask-restful resources for /v3/OS-INHERIT
import flask_restful
import functools
from oslo_log import log
from six.moves import http_client
@ -110,8 +111,10 @@ class OSInheritDomainGroupRolesResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:check_grant',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, group_id=group_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
group_id=group_id,
role_id=role_id))
PROVIDERS.assignment_api.get_grant(
domain_id=domain_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
@ -125,8 +128,10 @@ class OSInheritDomainGroupRolesResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:create_grant',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, group_id=group_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
group_id=group_id,
role_id=role_id))
PROVIDERS.assignment_api.create_grant(
domain_id=domain_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
@ -140,8 +145,10 @@ class OSInheritDomainGroupRolesResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:revoke_grant',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, group_id=group_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
group_id=group_id,
role_id=role_id))
PROVIDERS.assignment_api.delete_grant(
domain_id=domain_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
@ -157,8 +164,9 @@ class OSInheritDomainGroupRolesListResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:list_grants',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, group_id=group_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
group_id=group_id))
refs = PROVIDERS.assignment_api.list_grants(
domain_id=domain_id, group_id=group_id, inherited_to_projects=True)
return ks_flask.ResourceBase.wrap_collection(
@ -174,8 +182,10 @@ class OSInheritDomainUserRolesResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:check_grant',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, user_id=user_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
user_id=user_id,
role_id=role_id))
PROVIDERS.assignment_api.get_grant(
domain_id=domain_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
@ -189,8 +199,10 @@ class OSInheritDomainUserRolesResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:create_grant',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, user_id=user_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
user_id=user_id,
role_id=role_id))
PROVIDERS.assignment_api.create_grant(
domain_id=domain_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
@ -204,8 +216,10 @@ class OSInheritDomainUserRolesResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:revoke_grant',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, user_id=user_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
user_id=user_id,
role_id=role_id))
PROVIDERS.assignment_api.delete_grant(
domain_id=domain_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
@ -221,8 +235,9 @@ class OSInheritDomainUserRolesListResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:list_grants',
target_attr=_build_enforcement_target_attr(
domain_id=domain_id, user_id=user_id))
build_target=functools.partial(_build_enforcement_target_attr,
domain_id=domain_id,
user_id=user_id))
refs = PROVIDERS.assignment_api.list_grants(
domain_id=domain_id, user_id=user_id, inherited_to_projects=True)
return ks_flask.ResourceBase.wrap_collection(
@ -238,8 +253,10 @@ class OSInheritProjectUserResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:check_grant',
target_attr=_build_enforcement_target_attr(
project_id=project_id, user_id=user_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
project_id=project_id,
user_id=user_id,
role_id=role_id))
PROVIDERS.assignment_api.get_grant(
project_id=project_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
@ -253,8 +270,10 @@ class OSInheritProjectUserResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:create_grant',
target_attr=_build_enforcement_target_attr(
project_id=project_id, user_id=user_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
project_id=project_id,
user_id=user_id,
role_id=role_id))
PROVIDERS.assignment_api.create_grant(
project_id=project_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
@ -268,8 +287,10 @@ class OSInheritProjectUserResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:revoke_grant',
target_attr=_build_enforcement_target_attr(
project_id=project_id, user_id=user_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
project_id=project_id,
user_id=user_id,
role_id=role_id))
PROVIDERS.assignment_api.delete_grant(
project_id=project_id, user_id=user_id, role_id=role_id,
inherited_to_projects=True)
@ -285,8 +306,10 @@ class OSInheritProjectGroupResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:check_grant',
target_attr=_build_enforcement_target_attr(
project_id=project_id, group_id=group_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
project_id=project_id,
group_id=group_id,
role_id=role_id))
PROVIDERS.assignment_api.get_grant(
project_id=project_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
@ -300,8 +323,10 @@ class OSInheritProjectGroupResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:create_grant',
target_attr=_build_enforcement_target_attr(
project_id=project_id, group_id=group_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
project_id=project_id,
group_id=group_id,
role_id=role_id))
PROVIDERS.assignment_api.create_grant(
project_id=project_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)
@ -315,8 +340,10 @@ class OSInheritProjectGroupResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:revoke_grant',
target_attr=_build_enforcement_target_attr(
project_id=project_id, group_id=group_id, role_id=role_id))
build_target=functools.partial(_build_enforcement_target_attr,
project_id=project_id,
group_id=group_id,
role_id=role_id))
PROVIDERS.assignment_api.delete_grant(
project_id=project_id, group_id=group_id, role_id=role_id,
inherited_to_projects=True)

View File

@ -191,7 +191,7 @@ class RoleImplicationListResource(flask_restful.Resource):
GET/HEAD /v3/roles/{prior_role_id}/implies
"""
ENFORCER.enforce_call(action='identity:list_implied_roles',
target_attr=_build_enforcement_target_ref())
build_target=_build_enforcement_target_ref)
ref = PROVIDERS.role_api.list_implied_roles(prior_role_id)
implied_ids = [r['implied_role_id'] for r in ref]
response_json = shared.role_inference_response(prior_role_id)
@ -216,7 +216,7 @@ class RoleImplicationResource(flask_restful.Resource):
# Alternatively we can keep check_implied_role and reference
# ._get_implied_role instead.
ENFORCER.enforce_call(action='identity:check_implied_role',
target_attr=_build_enforcement_target_ref())
build_target=_build_enforcement_target_ref)
self.get(prior_role_id, implied_role_id)
# NOTE(morgan): Our API here breaks HTTP Spec. This should be evaluated
# for a future fix. This should just return the above "get" however,
@ -231,7 +231,7 @@ class RoleImplicationResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:get_implied_role',
target_attr=_build_enforcement_target_ref())
build_target=_build_enforcement_target_ref)
return self._get_implied_role(prior_role_id, implied_role_id)
def _get_implied_role(self, prior_role_id, implied_role_id):
@ -255,7 +255,7 @@ class RoleImplicationResource(flask_restful.Resource):
PUT /v3/roles/{prior_role_id}/implies/{implied_role_id}
"""
ENFORCER.enforce_call(action='identity:create_implied_role',
target_attr=_build_enforcement_target_ref())
build_target=_build_enforcement_target_ref)
PROVIDERS.role_api.create_implied_role(prior_role_id, implied_role_id)
response_json = self._get_implied_role(prior_role_id, implied_role_id)
return response_json, http_client.CREATED
@ -266,7 +266,7 @@ class RoleImplicationResource(flask_restful.Resource):
DELETE /v3/roles/{prior_role_id}/implies/{implied_role_id}
"""
ENFORCER.enforce_call(action='identity:delete_implied_role',
target_attr=_build_enforcement_target_ref())
build_target=_build_enforcement_target_ref)
PROVIDERS.role_api.delete_implied_role(prior_role_id, implied_role_id)
return None, http_client.NO_CONTENT

View File

@ -14,6 +14,7 @@
import flask
import flask_restful
import functools
from six.moves import http_client
from keystone.common import json_home
@ -57,7 +58,7 @@ class SystemUsersListResource(flask_restful.Resource):
GET/HEAD /system/users/{user_id}/roles
"""
ENFORCER.enforce_call(action='identity:list_system_grants_for_user',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
refs = PROVIDERS.assignment_api.list_system_grants_for_user(user_id)
return ks_flask.ResourceBase.wrap_collection(
refs, collection_name='roles')
@ -70,7 +71,7 @@ class SystemUsersResource(flask_restful.Resource):
GET/HEAD /system/users/{user_id}/roles/{role_id}
"""
ENFORCER.enforce_call(action='identity:check_system_grant_for_user',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.check_system_grant_for_user(user_id, role_id)
return None, http_client.NO_CONTENT
@ -80,7 +81,7 @@ class SystemUsersResource(flask_restful.Resource):
PUT /system/users/{user_id}/roles/{role_id}
"""
ENFORCER.enforce_call(action='identity:create_system_grant_for_user',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.create_system_grant_for_user(user_id, role_id)
return None, http_client.NO_CONTENT
@ -91,7 +92,9 @@ class SystemUsersResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:revoke_system_grant_for_user',
target_attr=_build_enforcement_target(allow_non_existing=True))
build_target=functools.partial(
_build_enforcement_target,
allow_non_existing=True))
PROVIDERS.assignment_api.delete_system_grant_for_user(user_id, role_id)
return None, http_client.NO_CONTENT
@ -103,7 +106,7 @@ class SystemGroupsRolesListResource(flask_restful.Resource):
GET/HEAD /system/groups/{group_id}/roles
"""
ENFORCER.enforce_call(action='identity:list_system_grants_for_group',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
refs = PROVIDERS.assignment_api.list_system_grants_for_group(group_id)
return ks_flask.ResourceBase.wrap_collection(
refs, collection_name='roles')
@ -116,7 +119,7 @@ class SystemGroupsRolestResource(flask_restful.Resource):
GET/HEAD /system/groups/{group_id}/roles/{role_id}
"""
ENFORCER.enforce_call(action='identity:check_system_grant_for_group',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.check_system_grant_for_group(
group_id, role_id)
return None, http_client.NO_CONTENT
@ -127,7 +130,7 @@ class SystemGroupsRolestResource(flask_restful.Resource):
PUT /system/groups/{group_id}/roles/{role_id}
"""
ENFORCER.enforce_call(action='identity:create_system_grant_for_group',
target_attr=_build_enforcement_target())
build_target=_build_enforcement_target)
PROVIDERS.assignment_api.create_system_grant_for_group(
group_id, role_id)
return None, http_client.NO_CONTENT
@ -139,7 +142,9 @@ class SystemGroupsRolestResource(flask_restful.Resource):
"""
ENFORCER.enforce_call(
action='identity:revoke_system_grant_for_group',
target_attr=_build_enforcement_target(allow_non_existing=True))
build_target=functools.partial(
_build_enforcement_target,
allow_non_existing=True))
PROVIDERS.assignment_api.delete_system_grant_for_group(
group_id, role_id)
return None, http_client.NO_CONTENT

View File

@ -245,7 +245,7 @@ class RBACEnforcer(object):
@classmethod
def enforce_call(cls, enforcer=None, action=None, target_attr=None,
member_target_type=None, member_target=None,
filters=None):
filters=None, build_target=None):
"""Enforce RBAC on the current request.
This will do some legwork and then instantiate the Enforcer if an
@ -282,6 +282,11 @@ class RBACEnforcer(object):
various "list" APIs and are un-used in the default
supplied policies.
:type filters: iterable
:param build_target: A function to build the target for enforcement.
This is explicitly done after authentication
in order to not leak existance data before
auth.
:type build_target: function
"""
# NOTE(morgan) everything in the policy_dict may be used by the policy
# DSL to action on RBAC and request information/response data.
@ -335,7 +340,7 @@ class RBACEnforcer(object):
policy_dict.update(flask.request.view_args)
# Get the Target Data Set.
if target_attr is None:
if target_attr is None and build_target is None:
try:
policy_dict.update(cls._extract_member_target_data(
member_target_type, member_target))
@ -362,7 +367,11 @@ class RBACEnforcer(object):
policy_dict.setdefault('target', {}).update(
subj_token_target_data)
else:
policy_dict['target'] = target_attr
if target_attr and build_target:
raise ValueError('Programming Error: A target_attr or '
'build_target must be provided, but not both')
policy_dict['target'] = target_attr or build_target()
# Pull the data from the submitted json body to generate
# appropriate input/target attributes, we take an explicit copy here

View File

@ -423,6 +423,43 @@ class TestRBACEnforcerRest(_TestRBACEnforcerBase):
self.assertEqual({}, self.enforcer._extract_member_target_data(
member_target={}, member_target_type=None))
def test_call_build_enforcement_target(self):
assertIn = self.assertIn
assertEq = self.assertEqual
ref_uuid = uuid.uuid4().hex
def _enforce_mock_func(credentials, action, target,
do_raise=True):
assertIn('target.domain.id', target)
assertEq(target['target.domain.id'], ref_uuid)
def _build_enforcement_target():
return {'domain': {'id': ref_uuid}}
self.useFixture(fixtures.MockPatchObject(
self.enforcer, '_enforce', _enforce_mock_func))
argument_id = uuid.uuid4().hex
with self.test_client() as c:
path = '/v3/auth/tokens'
body = self._auth_json()
r = c.post(
path,
json=body,
follow_redirects=True,
expected_status_code=201)
token_id = r.headers['X-Subject-Token']
c.get('%s/argument/%s' % (self.restful_api_url_prefix,
argument_id),
headers={'X-Auth-Token': token_id})
self.enforcer.enforce_call(
action='example:allowed',
build_target=_build_enforcement_target)
def test_policy_enforcer_action_decorator(self):
# Create a method that has an action pre-registered
action = 'example:allowed'