Add new default rules and mapping in policy base class
Cyborg Policy Default Refresh is one of the planned blueprints for victoria release, the specification[0] has been merged in ussuri. To be brief, we need to do the followings to incorporate authorization scopes into cyborg: 1. Add protection test for all APIs. A protection test is similar to an API test, but purely focused on the authoritative outcome.In other words, protection testing is sufficient when we can assert that a user is or isn’t allowed to do or see something. For example, users with a reader role on the system or a project shouldn’t be able to make writable changes. 2. Add the following applicable seven personas to cyborg and mark old ones as deprecated roles: * project reader * project member * project admin * system reader * system admin * system admin or owner * system or project reader 3. Rewrite check string(authorization rules) using new personas for all APIs 4. Update policy documentation on cyborg-doc page This patch refreshed cyborg default RBAC policy to scoped RBAC policy, and reorganized the policy framework into a more logical way: 1) added seven personas to basic policies and marked legacy roles as deprecated ones. 2) extract API_policies from policy.py to indenpendent policy files 3) extract authorize_wsgi.py out from policy.py [0]https://specs.openstack.org/openstack/cyborg-specs/specs/ussuri/approved/policy-defaults-refresh.html Story: 2007024 Task: 40835 Change-Id: I948d0202ddcd82a532c4de2c1850893cbfaf003d
This commit is contained in:
parent
27d5a7de39
commit
270d12e9a8
@ -26,10 +26,10 @@ from cyborg.api.controllers import types
|
||||
from cyborg.api.controllers.v2 import utils
|
||||
from cyborg.api.controllers.v2 import versions
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import authorize_wsgi
|
||||
from cyborg.common import constants
|
||||
from cyborg.common import exception
|
||||
from cyborg.common.i18n import _
|
||||
from cyborg.common import policy
|
||||
from cyborg import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -115,7 +115,7 @@ class ARQsController(base.CyborgController):
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@policy.authorize_wsgi("cyborg:arq", "create", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:arq", "create", False)
|
||||
@expose.expose(ARQCollection, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
def post(self, req):
|
||||
@ -169,7 +169,7 @@ class ARQsController(base.CyborgController):
|
||||
LOG.info('[arqs] post returned: %s', ret)
|
||||
return ret
|
||||
|
||||
@policy.authorize_wsgi("cyborg:arq", "get_one")
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:arq", "get_one")
|
||||
@expose.expose(ARQ, wtypes.text)
|
||||
def get_one(self, uuid):
|
||||
"""Get a single ARQ by UUID."""
|
||||
@ -177,7 +177,7 @@ class ARQsController(base.CyborgController):
|
||||
extarq = objects.ExtARQ.get(context, uuid)
|
||||
return ARQ.convert_with_links(extarq.arq)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:arq", "get_all", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:arq", "get_all", False)
|
||||
@expose.expose(ARQCollection, wtypes.text, types.uuid)
|
||||
def get_all(self, bind_state=None, instance=None):
|
||||
"""Retrieve a list of arqs."""
|
||||
@ -216,7 +216,7 @@ class ARQsController(base.CyborgController):
|
||||
LOG.info('[arqs:get_all] Returned: %s', ret)
|
||||
return ret
|
||||
|
||||
@policy.authorize_wsgi("cyborg:arq", "delete", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:arq", "delete", False)
|
||||
@expose.expose(None, wtypes.text, wtypes.text,
|
||||
status_code=http_client.NO_CONTENT)
|
||||
def delete(self, arqs=None, instance=None):
|
||||
@ -304,7 +304,7 @@ class ARQsController(base.CyborgController):
|
||||
reason = msg.format(instance_uuid)
|
||||
raise exception.PatchError(reason=reason)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:arq", "update", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:arq", "update", False)
|
||||
@expose.expose(None, body=types.jsontype,
|
||||
status_code=http_client.ACCEPTED)
|
||||
def patch(self, patch_list):
|
||||
|
@ -23,7 +23,7 @@ from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers import types
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import policy
|
||||
from cyborg.common import authorize_wsgi
|
||||
from cyborg import objects
|
||||
|
||||
|
||||
@ -110,7 +110,7 @@ class DeployablesController(base.CyborgController,
|
||||
DeployableCollection):
|
||||
"""REST controller for Deployables."""
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "get_one")
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:deployable", "get_one")
|
||||
@expose.expose(Deployable, types.uuid)
|
||||
def get_one(self, uuid):
|
||||
"""Retrieve information about the given deployable.
|
||||
@ -120,7 +120,7 @@ class DeployablesController(base.CyborgController,
|
||||
obj_dep = objects.Deployable.get(pecan.request.context, uuid)
|
||||
return self.convert_with_link(obj_dep)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:deployable", "get_all")
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:deployable", "get_all")
|
||||
@expose.expose(DeployableCollection, wtypes.ArrayType(types.FilterType))
|
||||
def get_all(self, filters=None):
|
||||
"""Retrieve a list of deployables.
|
||||
|
@ -26,8 +26,8 @@ from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers import types
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import authorize_wsgi
|
||||
from cyborg.common import exception
|
||||
from cyborg.common import policy
|
||||
from cyborg import objects
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@ -83,7 +83,7 @@ class DeviceProfilesController(base.CyborgController,
|
||||
DeviceProfileCollection):
|
||||
"""REST controller for Device Profiles."""
|
||||
|
||||
@policy.authorize_wsgi("cyborg:device_profile", "create", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "create", False)
|
||||
@expose.expose('json', body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
def post(self, req_devprof_list):
|
||||
@ -163,7 +163,7 @@ class DeviceProfilesController(base.CyborgController,
|
||||
|
||||
return api_obj_devprofs
|
||||
|
||||
@policy.authorize_wsgi("cyborg:device_profile", "get_all", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "get_all", False)
|
||||
@expose.expose('json', wtypes.text)
|
||||
def get_all(self, name=None):
|
||||
"""Retrieve a list of device profiles."""
|
||||
@ -180,7 +180,7 @@ class DeviceProfilesController(base.CyborgController,
|
||||
return wsme.api.Response(ret, status_code=http_client.OK,
|
||||
return_type=wsme.types.DictType)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:device_profile", "get_one")
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "get_one")
|
||||
@expose.expose('json', wtypes.text)
|
||||
def get_one(self, uuid):
|
||||
"""Retrieve a single device profile by uuid."""
|
||||
@ -201,7 +201,7 @@ class DeviceProfilesController(base.CyborgController,
|
||||
return wsme.api.Response(ret, status_code=http_client.OK,
|
||||
return_type=wsme.types.DictType)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:device_profile", "delete")
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:device_profile", "delete")
|
||||
@expose.expose(None, wtypes.text, status_code=http_client.NO_CONTENT)
|
||||
def delete(self, value):
|
||||
"""Delete one or more device_profiles.
|
||||
|
@ -23,7 +23,7 @@ from cyborg.api.controllers import base
|
||||
from cyborg.api.controllers import link
|
||||
from cyborg.api.controllers import types
|
||||
from cyborg.api import expose
|
||||
from cyborg.common import policy
|
||||
from cyborg.common import authorize_wsgi
|
||||
from cyborg import objects
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
@ -93,7 +93,7 @@ class DeviceCollection(base.APIBase):
|
||||
class DevicesController(base.CyborgController):
|
||||
"""REST controller for Devices."""
|
||||
|
||||
@policy.authorize_wsgi("cyborg:device", "get_one")
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:device", "get_one")
|
||||
@expose.expose(Device, wtypes.text)
|
||||
def get_one(self, uuid):
|
||||
"""Get a single device by UUID.
|
||||
@ -103,7 +103,7 @@ class DevicesController(base.CyborgController):
|
||||
device = objects.Device.get(context, uuid)
|
||||
return Device.convert_with_links(device)
|
||||
|
||||
@policy.authorize_wsgi("cyborg:device", "get_all", False)
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:device", "get_all", False)
|
||||
@expose.expose(DeviceCollection, wtypes.text, wtypes.text, wtypes.text,
|
||||
wtypes.ArrayType(types.FilterType))
|
||||
def get_all(self, type=None, vendor=None, hostname=None, filters=None):
|
||||
|
167
cyborg/common/authorize_wsgi.py
Normal file
167
cyborg/common/authorize_wsgi.py
Normal file
@ -0,0 +1,167 @@
|
||||
# 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 Authorize Engine For Cyborg."""
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
from oslo_policy import policy
|
||||
from oslo_versionedobjects import base as object_base
|
||||
import pecan
|
||||
import wsme
|
||||
|
||||
from cyborg.common import exception
|
||||
from cyborg import policies
|
||||
|
||||
|
||||
_ENFORCER = None
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
@lockutils.synchronized('policy_enforcer', 'cyborg-')
|
||||
def init_enforcer(policy_file=None, rules=None,
|
||||
default_rule=None, use_conf=True):
|
||||
"""Synchronously initializes the policy enforcer
|
||||
:param policy_file: Custom policy file to use, if none is specified,
|
||||
`CONF.oslo_policy.policy_file` will be used.
|
||||
:param rules: Default dictionary / Rules to use. It will be
|
||||
considered just in the first instantiation.
|
||||
:param default_rule: Default rule to use,
|
||||
CONF.oslo_policy.policy_default_rule will
|
||||
be used if none is specified.
|
||||
:param use_conf: Whether to load rules from config file.
|
||||
"""
|
||||
global _ENFORCER
|
||||
|
||||
if _ENFORCER:
|
||||
return
|
||||
|
||||
# NOTE: Register defaults for policy-in-code here so that they are
|
||||
# loaded exactly once - when this module-global is initialized.
|
||||
# Defining these in the relevant API modules won't work
|
||||
# because API classes lack singletons and don't use globals.
|
||||
_ENFORCER = policy.Enforcer(CONF, policy_file=policy_file,
|
||||
rules=rules,
|
||||
default_rule=default_rule,
|
||||
use_conf=use_conf)
|
||||
_ENFORCER.register_defaults(policies.list_policies())
|
||||
|
||||
|
||||
def get_enforcer():
|
||||
"""Provides access to the single accelerator of policy enforcer."""
|
||||
global _ENFORCER
|
||||
|
||||
if not _ENFORCER:
|
||||
init_enforcer()
|
||||
|
||||
return _ENFORCER
|
||||
|
||||
|
||||
# NOTE: We can't call these methods from within decorators because the
|
||||
# 'target' and 'creds' parameter must be fetched from the call time
|
||||
# context-local pecan.request magic variable, but decorators are compiled
|
||||
# at module-load time.
|
||||
|
||||
|
||||
def authorize(rule, target, creds, do_raise=False, *args, **kwargs):
|
||||
"""A shortcut for policy.Enforcer.authorize()
|
||||
Checks authorization of a rule against the target and credentials, and
|
||||
raises an exception if the rule is not defined.
|
||||
"""
|
||||
enforcer = get_enforcer()
|
||||
try:
|
||||
return enforcer.authorize(rule, target, creds, do_raise=do_raise,
|
||||
*args, **kwargs)
|
||||
except policy.PolicyNotAuthorized:
|
||||
raise exception.HTTPForbidden(resource=rule)
|
||||
|
||||
|
||||
# This decorator MUST appear first (the outermost decorator)
|
||||
# on an API method for it to work correctly
|
||||
def authorize_wsgi(api_name, act=None, need_target=True):
|
||||
"""This is a decorator to simplify wsgi action policy rule check.
|
||||
:param api_name: The collection name to be evaluate.
|
||||
:param act: The function name of wsgi action.
|
||||
:param need_target: Whether need target for authorization. Such as,
|
||||
when create some resource , maybe target is not needed.
|
||||
example:
|
||||
from cyborg.common import authorize_wsgi
|
||||
class AcceleratorController(rest.RestController):
|
||||
....
|
||||
@authorize_wsgi.authorize_wsgi("cyborg:accelerator",
|
||||
"create", False)
|
||||
@wsme_pecan.wsexpose(Accelerator, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
def post(self, values):
|
||||
...
|
||||
"""
|
||||
def wraper(fn):
|
||||
action = '%s:%s' % (api_name, act or fn.__name__)
|
||||
|
||||
# In this authorize method, we return a dict data when authorization
|
||||
# fails or exception comes out. Maybe we can consider to use
|
||||
# wsme.api.Response in future.
|
||||
def return_error(resp_status):
|
||||
exception_info = sys.exc_info()
|
||||
orig_exception = exception_info[1]
|
||||
orig_code = getattr(orig_exception, 'code', None)
|
||||
pecan.response.status = orig_code or resp_status
|
||||
data = wsme.api.format_exception(
|
||||
exception_info,
|
||||
pecan.conf.get('wsme', {}).get('debug', False)
|
||||
)
|
||||
del exception_info
|
||||
return data
|
||||
|
||||
@functools.wraps(fn)
|
||||
def handle(self, *args, **kwargs):
|
||||
context = pecan.request.context
|
||||
credentials = context.to_policy_values()
|
||||
credentials['is_admin'] = context.is_admin
|
||||
target = {}
|
||||
# maybe we can pass "_get_resource" to authorize_wsgi
|
||||
if need_target and hasattr(self, "_get_resource"):
|
||||
try:
|
||||
resource = getattr(self, "_get_resource")(*args, **kwargs)
|
||||
# just support object, other type will just keep target as
|
||||
# empty, then follow authorize method will fail and throw
|
||||
# an exception
|
||||
if isinstance(resource,
|
||||
object_base.VersionedObjectDictCompat):
|
||||
target = {'project_id': resource.project_id,
|
||||
'user_id': resource.user_id}
|
||||
except Exception:
|
||||
return return_error(500)
|
||||
elif need_target:
|
||||
# if developer do not set _get_resource, just set target as
|
||||
# empty, then follow authorize method will fail and throw an
|
||||
# exception
|
||||
target = {}
|
||||
else:
|
||||
# for create method, before resource exsites, we can check the
|
||||
# the credentials with itself.
|
||||
target = {'project_id': context.tenant,
|
||||
'user_id': context.user}
|
||||
|
||||
try:
|
||||
authorize(action, target, credentials, do_raise=True)
|
||||
except Exception:
|
||||
return return_error(403)
|
||||
|
||||
return fn(self, *args, **kwargs)
|
||||
|
||||
return handle
|
||||
|
||||
return wraper
|
@ -13,61 +13,11 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
"""Policy Engine For Cyborg."""
|
||||
|
||||
import functools
|
||||
import sys
|
||||
|
||||
from oslo_concurrency import lockutils
|
||||
from oslo_config import cfg
|
||||
from oslo_log import log
|
||||
"""legacy old_policies, the following old_policies will be removed once
|
||||
new policies are implemented.
|
||||
"""
|
||||
from oslo_policy import policy
|
||||
from oslo_versionedobjects import base as object_base
|
||||
import pecan
|
||||
import wsme
|
||||
|
||||
from cyborg.common import exception
|
||||
|
||||
|
||||
_ENFORCER = None
|
||||
CONF = cfg.CONF
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
|
||||
default_policies = [
|
||||
# Legacy setting, don't remove. Likely to be overridden by operators who
|
||||
# forget to update their policy.json configuration file.
|
||||
# This gets rolled into the new "is_admin" rule below.
|
||||
policy.RuleDefault('admin_api',
|
||||
'role:admin or role:administrator',
|
||||
description='Legacy rule for cloud admin access'),
|
||||
# is_public_api is set in the environment from AuthTokenMiddleware
|
||||
policy.RuleDefault('public_api',
|
||||
'is_public_api:True',
|
||||
description='Internal flag for public API routes'),
|
||||
# The policy check "@" will always accept an access. The empty list
|
||||
# (``[]``) or the empty string (``""``) is equivalent to the "@"
|
||||
policy.RuleDefault('allow',
|
||||
'@',
|
||||
description='any access will be passed'),
|
||||
# the policy check "!" will always reject an access.
|
||||
policy.RuleDefault('deny',
|
||||
'!',
|
||||
description='all access will be forbidden'),
|
||||
policy.RuleDefault('is_admin',
|
||||
'rule:admin_api',
|
||||
description='Full read/write API access'),
|
||||
policy.RuleDefault('admin_or_owner',
|
||||
'is_admin:True or project_id:%(project_id)s',
|
||||
description='Admin or owner API access'),
|
||||
policy.RuleDefault('admin_or_user',
|
||||
'is_admin:True or user_id:%(user_id)s',
|
||||
description='Admin or user API access'),
|
||||
policy.RuleDefault('default',
|
||||
'rule:admin_or_owner',
|
||||
description='Default API access rule'),
|
||||
]
|
||||
|
||||
# NOTE: to follow policy-in-code spec, we define defaults for
|
||||
# the granular policies in code, rather than in policy.json.
|
||||
# All of these may be overridden by configuration, but we can
|
||||
@ -136,153 +86,3 @@ fpga_policies = [
|
||||
'rule:allow',
|
||||
description='Update fpga records'),
|
||||
]
|
||||
|
||||
|
||||
def list_policies():
|
||||
return default_policies \
|
||||
+ fpga_policies \
|
||||
+ accelerator_request_policies \
|
||||
+ device_profile_policies \
|
||||
+ device_policies \
|
||||
+ deployable_policies
|
||||
|
||||
|
||||
@lockutils.synchronized('policy_enforcer', 'cyborg-')
|
||||
def init_enforcer(policy_file=None, rules=None,
|
||||
default_rule=None, use_conf=True):
|
||||
"""Synchronously initializes the policy enforcer
|
||||
|
||||
:param policy_file: Custom policy file to use, if none is specified,
|
||||
`CONF.oslo_policy.policy_file` will be used.
|
||||
:param rules: Default dictionary / Rules to use. It will be
|
||||
considered just in the first instantiation.
|
||||
:param default_rule: Default rule to use,
|
||||
CONF.oslo_policy.policy_default_rule will
|
||||
be used if none is specified.
|
||||
:param use_conf: Whether to load rules from config file.
|
||||
|
||||
"""
|
||||
global _ENFORCER
|
||||
|
||||
if _ENFORCER:
|
||||
return
|
||||
|
||||
# NOTE: Register defaults for policy-in-code here so that they are
|
||||
# loaded exactly once - when this module-global is initialized.
|
||||
# Defining these in the relevant API modules won't work
|
||||
# because API classes lack singletons and don't use globals.
|
||||
_ENFORCER = policy.Enforcer(CONF, policy_file=policy_file,
|
||||
rules=rules,
|
||||
default_rule=default_rule,
|
||||
use_conf=use_conf)
|
||||
_ENFORCER.register_defaults(list_policies())
|
||||
|
||||
|
||||
def get_enforcer():
|
||||
"""Provides access to the single accelerator of policy enforcer."""
|
||||
global _ENFORCER
|
||||
|
||||
if not _ENFORCER:
|
||||
init_enforcer()
|
||||
|
||||
return _ENFORCER
|
||||
|
||||
|
||||
# NOTE: We can't call these methods from within decorators because the
|
||||
# 'target' and 'creds' parameter must be fetched from the call time
|
||||
# context-local pecan.request magic variable, but decorators are compiled
|
||||
# at module-load time.
|
||||
|
||||
|
||||
def authorize(rule, target, creds, do_raise=False, *args, **kwargs):
|
||||
"""A shortcut for policy.Enforcer.authorize()
|
||||
|
||||
Checks authorization of a rule against the target and credentials, and
|
||||
raises an exception if the rule is not defined.
|
||||
"""
|
||||
enforcer = get_enforcer()
|
||||
try:
|
||||
return enforcer.authorize(rule, target, creds, do_raise=do_raise,
|
||||
*args, **kwargs)
|
||||
except policy.PolicyNotAuthorized:
|
||||
raise exception.HTTPForbidden(resource=rule)
|
||||
|
||||
|
||||
# This decorator MUST appear first (the outermost decorator)
|
||||
# on an API method for it to work correctly
|
||||
def authorize_wsgi(api_name, act=None, need_target=True):
|
||||
"""This is a decorator to simplify wsgi action policy rule check.
|
||||
|
||||
:param api_name: The collection name to be evaluate.
|
||||
:param act: The function name of wsgi action.
|
||||
:param need_target: Whether need target for authorization. Such as,
|
||||
when create some resource , maybe target is not needed.
|
||||
|
||||
example:
|
||||
from cyborg.common import policy
|
||||
class AcceleratorController(rest.RestController):
|
||||
....
|
||||
@policy.authorize_wsgi("cyborg:accelerator", "create", False)
|
||||
@wsme_pecan.wsexpose(Accelerator, body=types.jsontype,
|
||||
status_code=http_client.CREATED)
|
||||
def post(self, values):
|
||||
...
|
||||
"""
|
||||
def wraper(fn):
|
||||
action = '%s:%s' % (api_name, act or fn.__name__)
|
||||
|
||||
# In this authorize method, we return a dict data when authorization
|
||||
# fails or exception comes out. Maybe we can consider to use
|
||||
# wsme.api.Response in future.
|
||||
def return_error(resp_status):
|
||||
exception_info = sys.exc_info()
|
||||
orig_exception = exception_info[1]
|
||||
orig_code = getattr(orig_exception, 'code', None)
|
||||
pecan.response.status = orig_code or resp_status
|
||||
data = wsme.api.format_exception(
|
||||
exception_info,
|
||||
pecan.conf.get('wsme', {}).get('debug', False)
|
||||
)
|
||||
del exception_info
|
||||
return data
|
||||
|
||||
@functools.wraps(fn)
|
||||
def handle(self, *args, **kwargs):
|
||||
context = pecan.request.context
|
||||
credentials = context.to_policy_values()
|
||||
credentials['is_admin'] = context.is_admin
|
||||
target = {}
|
||||
# maybe we can pass "_get_resource" to authorize_wsgi
|
||||
if need_target and hasattr(self, "_get_resource"):
|
||||
try:
|
||||
resource = getattr(self, "_get_resource")(*args, **kwargs)
|
||||
# just support object, other type will just keep target as
|
||||
# empty, then follow authorize method will fail and throw
|
||||
# an exception
|
||||
if isinstance(resource,
|
||||
object_base.VersionedObjectDictCompat):
|
||||
target = {'project_id': resource.project_id,
|
||||
'user_id': resource.user_id}
|
||||
except Exception:
|
||||
return return_error(500)
|
||||
elif need_target:
|
||||
# if developer do not set _get_resource, just set target as
|
||||
# empty, then follow authorize method will fail and throw an
|
||||
# exception
|
||||
target = {}
|
||||
else:
|
||||
# for create method, before resource exsites, we can check the
|
||||
# the credentials with itself.
|
||||
target = {'project_id': context.tenant,
|
||||
'user_id': context.user}
|
||||
|
||||
try:
|
||||
authorize(action, target, credentials, do_raise=True)
|
||||
except Exception:
|
||||
return return_error(403)
|
||||
|
||||
return fn(self, *args, **kwargs)
|
||||
|
||||
return handle
|
||||
|
||||
return wraper
|
||||
|
33
cyborg/policies/__init__.py
Normal file
33
cyborg/policies/__init__.py
Normal file
@ -0,0 +1,33 @@
|
||||
# Copyright 2020 ZTE Corporation.
|
||||
# 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.
|
||||
|
||||
|
||||
import itertools
|
||||
|
||||
from cyborg.common import policy as old_policy
|
||||
from cyborg.policies import base
|
||||
|
||||
|
||||
def list_policies():
|
||||
return itertools.chain(
|
||||
base.list_policies(),
|
||||
# NOTE(yumeng)old_policies will also be loaded before they are replaced
|
||||
# by new policies
|
||||
old_policy.device_profile_policies,
|
||||
old_policy.device_policies,
|
||||
old_policy.deployable_policies,
|
||||
old_policy.accelerator_request_policies,
|
||||
old_policy.fpga_policies,
|
||||
)
|
167
cyborg/policies/base.py
Normal file
167
cyborg/policies/base.py
Normal file
@ -0,0 +1,167 @@
|
||||
# Copyright 2020 ZTE Corporation.
|
||||
# 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.
|
||||
|
||||
from oslo_log import versionutils
|
||||
from oslo_policy import policy
|
||||
|
||||
# All legacy policy and new policy mapping for all V2 APIs can be found
|
||||
# here:https://wiki.openstack.org/wiki/Cyborg/Policy
|
||||
|
||||
|
||||
# TODO(yumeng) Special string ``system_scope:all``
|
||||
# We are explicitly setting system_scope:all in these check strings because
|
||||
# they provide backwards compatibility in the event a deployment sets
|
||||
# ``cyborg.conf [oslo_policy] enforce_scope = False``, which the default.
|
||||
# Otherwise, this might open up APIs to be more permissive unintentionally if a
|
||||
# deployment isn't enforcing scope. For example, the new rule for action
|
||||
# 'cyborg:device_profile:create' will be System Scoped Admin with
|
||||
# ``role:admin`` and scope_type=['system']. However, it would be possible for
|
||||
# users with the ``admin`` role on a project to access the
|
||||
# 'cyborg:device_profile:create' until enforce_scope=True is set by default.
|
||||
# Once cyborg defaults ``cyborg.conf [oslo_policy] enforce_scope = True``,
|
||||
# the the ``system_scope:all`` bits of these check strings
|
||||
# can be removed since that will be handled automatically by scope_types in
|
||||
# oslo.policy's RuleDefault objects.
|
||||
SYSTEM_ADMIN = 'rule:system_admin_api'
|
||||
SYSTEM_READER = 'rule:system_reader_api'
|
||||
PROJECT_ADMIN = 'rule:project_admin_api'
|
||||
PROJECT_MEMBER = 'rule:project_member_api'
|
||||
PROJECT_READER = 'rule:project_reader_api'
|
||||
PROJECT_MEMBER_OR_SYSTEM_ADMIN = 'rule:system_admin_or_owner'
|
||||
PROJECT_READER_OR_SYSTEM_READER = 'rule:system_or_project_reader'
|
||||
|
||||
# NOTE(yumeng): Keystone already support implied roles means assignment
|
||||
# of one role implies the assignment of another. New defaults roles
|
||||
# `reader`, `member` also has been added in bootstrap. If the bootstrap
|
||||
# process is re-run, and a `reader`, `member`, or `admin` role already
|
||||
# exists, a role implication chain will be created: `admin` implies
|
||||
# `member` implies `reader`.
|
||||
# For example: If we give access to 'reader' it means the 'admin' and
|
||||
# 'member' also get access.
|
||||
|
||||
# NOTE(yumeng) the rules listed include both old rules and new rules.
|
||||
# legacy rules list: 'public_api','allow','deny','admin_api','is_admin',
|
||||
# 'admin_or_owner','admin_or_user'.
|
||||
# new rules list: system_admin_api,system_reader_api,project_admin_api,
|
||||
# project_member_api, project_reader_api, system_admin_or_owner,
|
||||
# system_or_project_reader .
|
||||
default_policies = [
|
||||
policy.RuleDefault(
|
||||
name="system_admin_api",
|
||||
check_str='role:admin and system_scope:all',
|
||||
description="Default rule for System Admin APIs."),
|
||||
policy.RuleDefault(
|
||||
name="system_reader_api",
|
||||
check_str="role:reader and system_scope:all",
|
||||
description="Default rule for System level read only APIs."),
|
||||
policy.RuleDefault(
|
||||
name="project_admin_api",
|
||||
check_str="role:admin and project_id:%(project_id)s",
|
||||
description="Default rule for Project level admin APIs."),
|
||||
policy.RuleDefault(
|
||||
name="project_member_api",
|
||||
check_str="role:member and project_id:%(project_id)s",
|
||||
description="Default rule for Project level non admin APIs."),
|
||||
policy.RuleDefault(
|
||||
name="project_reader_api",
|
||||
check_str="role:reader and project_id:%(project_id)s",
|
||||
description="Default rule for Project level read only APIs."),
|
||||
policy.RuleDefault(
|
||||
name="system_admin_or_owner",
|
||||
check_str="rule:system_admin_api or rule:project_member_api",
|
||||
description="Default rule for system_admin+owner APIs."),
|
||||
policy.RuleDefault(
|
||||
name="system_or_project_reader",
|
||||
check_str="rule:system_reader_api or rule:project_reader_api",
|
||||
description="Default rule for System+Project read only APIs.")
|
||||
]
|
||||
|
||||
DEPRECATED_REASON = """
|
||||
Cyborg API policies are introducing new default roles with scope_type
|
||||
capabilities. We will start to deprecate old policies from WALLABY release,
|
||||
and are going to ignore all the old policies silently from X release.
|
||||
Be sure to take these new defaults into consideration if you are
|
||||
relying on overrides in your deployment for the policy API.
|
||||
"""
|
||||
|
||||
deprecated_default = 'rule:admin_or_owner'
|
||||
deprecated_is_admin = 'rule:is_admin'
|
||||
deprecated_default_policies = [
|
||||
# is_public_api is set in the environment from AuthTokenMiddleware
|
||||
policy.RuleDefault(
|
||||
name='public_api',
|
||||
check_str='is_public_api:True',
|
||||
description='legacy rule of Internal flag for public API routes',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
# The policy check "@" will always accept an access. The empty list
|
||||
# (``[]``) or the empty string (``""``) is equivalent to the "@"
|
||||
policy.RuleDefault(
|
||||
name='allow',
|
||||
check_str='@',
|
||||
description='legacy rule: any access will be passed',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
# the policy check "!" will always reject an access.
|
||||
policy.RuleDefault(
|
||||
name='deny',
|
||||
check_str='!',
|
||||
description='legacy rule: all access will be forbidden',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
policy.RuleDefault(
|
||||
name='default',
|
||||
check_str='rule:admin_or_owner',
|
||||
description='Legacy rule for default rule',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
policy.RuleDefault(
|
||||
name='admin_api',
|
||||
check_str='role:admin or role:administrator',
|
||||
description='Legacy rule for cloud admin access',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
policy.RuleDefault(
|
||||
name='is_admin',
|
||||
check_str='rule:admin_api',
|
||||
description='Full read/write API access',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
policy.RuleDefault(
|
||||
name='admin_or_owner',
|
||||
check_str='is_admin:True or project_id:%(project_id)s',
|
||||
description='Admin or owner API access',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
policy.RuleDefault(
|
||||
name='admin_or_user',
|
||||
check_str='is_admin:True or user_id:%(user_id)s',
|
||||
description='Admin or user API access',
|
||||
deprecated_for_removal=True,
|
||||
deprecated_reason=DEPRECATED_REASON,
|
||||
deprecated_since=versionutils.deprecated.WALLABY),
|
||||
]
|
||||
|
||||
|
||||
def list_policies():
|
||||
return default_policies \
|
||||
+ deprecated_default_policies
|
@ -19,7 +19,7 @@ import fixtures
|
||||
from oslo_config import cfg
|
||||
from oslo_policy import opts as policy_opts
|
||||
|
||||
from cyborg.common import policy as cyborg_policy
|
||||
from cyborg.common import authorize_wsgi as cyborg_policy
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
Loading…
Reference in New Issue
Block a user