diff --git a/distributedcloud/dcdbsync/api/enforcer.py b/distributedcloud/dcdbsync/api/enforcer.py deleted file mode 100644 index ae6c3b27a..000000000 --- a/distributedcloud/dcdbsync/api/enforcer.py +++ /dev/null @@ -1,76 +0,0 @@ -# Copyright 2017 Ericsson AB. -# -# 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. -# -# Copyright (c) 2019 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -"""Policy enforcer for DC DBsync Agent.""" - -from oslo_config import cfg -from oslo_policy import policy - -from dcdbsync.common import exceptions as exc - - -_ENFORCER = None - - -def enforce(action, context, target=None, do_raise=True, - exc=exc.NotAuthorized): - """Verify that the action is valid on the target in this context. - - :param action: String, representing the action to be checked. - This should be colon separated for clarity. - i.e. ``sync:list`` - :param context: DC DBsync context. - :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. ``{'project_id': context.project}`` - :param do_raise: if True (the default), raises specified exception. - :param exc: Exception to be raised if not authorized. Default is - dcdbsync.common.exceptions.NotAuthorized. - - :return: returns True if authorized and False if not authorized and - do_raise is False. - """ - if cfg.CONF.auth_strategy != 'keystone': - # Policy enforcement is supported now only with Keystone - # authentication. - return - - target_obj = { - 'project_id': context.project, - 'user_id': context.user, - } - - target_obj.update(target or {}) - _ensure_enforcer_initialization() - - try: - _ENFORCER.enforce(action, target_obj, context.to_dict(), - do_raise=do_raise, exc=exc) - return True - - except Exception: - return False - - -def _ensure_enforcer_initialization(): - global _ENFORCER - if not _ENFORCER: - _ENFORCER = policy.Enforcer(cfg.CONF) - _ENFORCER.load_rules() diff --git a/distributedcloud/dcdbsync/api/policies/__init__.py b/distributedcloud/dcdbsync/api/policies/__init__.py new file mode 100644 index 000000000..e6894f72d --- /dev/null +++ b/distributedcloud/dcdbsync/api/policies/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import itertools + +from dcdbsync.api.policies import base + + +def list_rules(): + return itertools.chain( + base.list_rules() + ) diff --git a/distributedcloud/dcdbsync/api/policies/base.py b/distributedcloud/dcdbsync/api/policies/base.py new file mode 100644 index 000000000..a4455a24a --- /dev/null +++ b/distributedcloud/dcdbsync/api/policies/base.py @@ -0,0 +1,30 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from oslo_policy import policy + +ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects' +READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects' + + +base_rules = [ + policy.RuleDefault( + name=ADMIN_IN_SYSTEM_PROJECTS, + check_str='role:admin and (project_name:admin or ' + + 'project_name:services)', + description="Base rule.", + ), + policy.RuleDefault( + name=READER_IN_SYSTEM_PROJECTS, + check_str='role:reader and (project_name:admin or ' + + 'project_name:services)', + description="Base rule." + ) +] + + +def list_rules(): + return base_rules diff --git a/distributedcloud/dcdbsync/api/policy.py b/distributedcloud/dcdbsync/api/policy.py new file mode 100644 index 000000000..6f3a942c9 --- /dev/null +++ b/distributedcloud/dcdbsync/api/policy.py @@ -0,0 +1,62 @@ +# Copyright (c) 2011 OpenStack Foundation +# 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. +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +"""Policy Engine For DC.""" + +from dcdbsync.api import policies as controller_policies +from oslo_config import cfg +from oslo_policy import policy +from webob import exc + + +CONF = cfg.CONF +_ENFORCER = None + + +def reset(): + """Discard current Enforcer object.""" + global _ENFORCER + _ENFORCER = None + + +def init(policy_file='policy.yaml'): + """Init an Enforcer class. + + :param policy_file: Custom policy file to be used. + + :return: Returns a Enforcer instance. + """ + global _ENFORCER + if not _ENFORCER: + + # https://docs.openstack.org/oslo.policy/latest/user/usage.html + _ENFORCER = policy.Enforcer(CONF, + policy_file=policy_file, + default_rule='default', + use_conf=True, + overwrite=True) + _ENFORCER.register_defaults(controller_policies.list_rules()) + return _ENFORCER + + +def authorize(rule, target, creds, do_raise=True): + """A wrapper around 'authorize' from 'oslo_policy.policy'.""" + init() + return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise, + exc=exc.HTTPForbidden) diff --git a/distributedcloud/dcdbsync/common/context.py b/distributedcloud/dcdbsync/common/context.py index 0c7bc2a7c..532147022 100644 --- a/distributedcloud/dcdbsync/common/context.py +++ b/distributedcloud/dcdbsync/common/context.py @@ -10,7 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2019, 2022 Wind River Systems, Inc. +# Copyright (c) 2019-2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -21,7 +21,8 @@ from pecan import hooks from oslo_context import context as base_context from oslo_utils import encodeutils -from dcdbsync.common import policy +from dcdbsync.api.policies import base as base_policy +from dcdbsync.api import policy from dcdbsync.db.identity import api as db_api ALLOWED_WITHOUT_AUTH = '/' @@ -78,9 +79,9 @@ class RequestContext(base_context.RequestContext): # Check user is admin or not if is_admin is None: - self.is_admin = policy.enforce(self, 'context_is_admin', - target={'project': self.project}, - do_raise=False) + self.is_admin = policy.authorize( + base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, self.to_dict(), + do_raise=False) else: self.is_admin = is_admin diff --git a/distributedcloud/dcdbsync/common/policy.py b/distributedcloud/dcdbsync/common/policy.py deleted file mode 100644 index 17114f61d..000000000 --- a/distributedcloud/dcdbsync/common/policy.py +++ /dev/null @@ -1,54 +0,0 @@ -# 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. -# -# Copyright (c) 2019 Wind River Systems, Inc. -# -# SPDX-License-Identifier: Apache-2.0 -# - -""" -Policy Engine For DC DBsync Agent -""" - -# from oslo_concurrency import lockutils -from oslo_config import cfg -from oslo_policy import policy - -from dcdbsync.common import exceptions - -POLICY_ENFORCER = None -CONF = cfg.CONF - - -# @lockutils.synchronized('policy_enforcer', 'dcdbsync-') -def _get_enforcer(policy_file=None, rules=None, default_rule=None): - - global POLICY_ENFORCER - - if POLICY_ENFORCER is None: - POLICY_ENFORCER = policy.Enforcer(CONF, - policy_file=policy_file, - rules=rules, - default_rule=default_rule) - return POLICY_ENFORCER - - -def enforce(context, rule, target, do_raise=True, *args, **kwargs): - - enforcer = _get_enforcer() - credentials = context.to_dict() - target = target or {} - if do_raise: - kwargs.update(exc=exceptions.Forbidden) - - return enforcer.enforce(rule, target, credentials, do_raise, - *args, **kwargs) diff --git a/distributedcloud/dcmanager/api/api_config.py b/distributedcloud/dcmanager/api/api_config.py index 65db47dbf..f0291e01a 100644 --- a/distributedcloud/dcmanager/api/api_config.py +++ b/distributedcloud/dcmanager/api/api_config.py @@ -1,5 +1,5 @@ # Copyright 2015 Huawei Technologies Co., Ltd. -# Copyright (c) 2017, 2019, 2021 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -27,9 +27,6 @@ from oslo_config import cfg from oslo_log import log as logging from dcmanager.common.i18n import _ - - -# from dcmanager import policy from dcmanager.common import version LOG = logging.getLogger(__name__) @@ -98,7 +95,10 @@ def reset_service(): def test_init(): # Register the configuration options cfg.CONF.register_opts(common_opts) - logging.register_options(cfg.CONF) + try: + logging.register_options(cfg.CONF) + except cfg.ArgsAlreadyParsedError: + pass setup_logging() diff --git a/distributedcloud/dcmanager/api/controllers/restcomm.py b/distributedcloud/dcmanager/api/controllers/restcomm.py index 336754f7e..63e919d05 100644 --- a/distributedcloud/dcmanager/api/controllers/restcomm.py +++ b/distributedcloud/dcmanager/api/controllers/restcomm.py @@ -1,5 +1,5 @@ # Copyright (c) 2015 Huawei Tech. Co., Ltd. -# Copyright (c) 2017, 2019, 2021, 2022 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -41,3 +41,13 @@ def extract_context_from_environ(): context_paras['is_admin'] = 'admin' in role.split(',') return k_context.RequestContext(**context_paras) + + +def extract_credentials_for_policy(): + context_paras = {'project_name': 'HTTP_X_PROJECT_NAME', + 'roles': 'HTTP_X_ROLE'} + environ = request.environ + for key, val in context_paras.items(): + context_paras[key] = environ.get(val) + context_paras['roles'] = context_paras['roles'].split(',') + return context_paras diff --git a/distributedcloud/dcmanager/api/controllers/v1/alarm_manager.py b/distributedcloud/dcmanager/api/controllers/v1/alarm_manager.py index 138049a79..691276988 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/alarm_manager.py +++ b/distributedcloud/dcmanager/api/controllers/v1/alarm_manager.py @@ -1,5 +1,5 @@ # Copyright (c) 2017 Ericsson AB. -# Copyright (c) 2017-2021 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -16,6 +16,8 @@ # from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import alarm_manager as alarm_manager_policy +from dcmanager.api import policy from dcmanager.common import consts from dcmanager.db import api as db_api @@ -63,6 +65,8 @@ class SubcloudAlarmController(object): """Get List of alarm summarys """ + policy.authorize(alarm_manager_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) return self._get_alarm_aggregates() def _get_alarm_summary(self): @@ -79,4 +83,6 @@ class SubcloudAlarmController(object): """Get an agregate of all subcloud status """ + policy.authorize(alarm_manager_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) return self._get_alarm_summary() diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_backup.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_backup.py index 2398494b7..74f3561eb 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_backup.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_backup.py @@ -22,6 +22,8 @@ from yaml.scanner import ScannerError from dccommon import consts as dccommon_consts from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import subcloud_backup as subcloud_backup_policy +from dcmanager.api import policy from dcmanager.common import consts from dcmanager.common.i18n import _ from dcmanager.common import utils @@ -213,6 +215,9 @@ class SubcloudBackupController(object): def post(self): """Create a new subcloud backup.""" + policy.authorize(subcloud_backup_policy.POLICY_ROOT % "create", {}, + restcomm.extract_credentials_for_policy()) + context = restcomm.extract_context_from_environ() payload = self._get_backup_payload(request) diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py index b5985c30a..7c81ba79f 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_deploy.py @@ -12,7 +12,7 @@ # License for the specific language governing permissions and limitations # under the License. # -# Copyright (c) 2020, 2022 Wind River Systems, Inc. +# Copyright (c) 2020-2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -27,6 +27,9 @@ import pecan from pecan import expose from pecan import request +from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import subcloud_deploy as subcloud_deploy_policy +from dcmanager.api import policy from dcmanager.common import consts from dcmanager.common.i18n import _ from dcmanager.common import utils @@ -78,6 +81,8 @@ class SubcloudDeployController(object): @utils.synchronized(LOCK_NAME) @index.when(method='POST', template='json') def post(self): + policy.authorize(subcloud_deploy_policy.POLICY_ROOT % "upload", {}, + restcomm.extract_credentials_for_policy()) deploy_dicts = dict() missing_options = set() for f in consts.DEPLOY_COMMON_FILE_OPTIONS: @@ -126,6 +131,8 @@ class SubcloudDeployController(object): def get(self): """Get the subcloud deploy files that has been uploaded and stored""" + policy.authorize(subcloud_deploy_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) deploy_dicts = dict() for f in consts.DEPLOY_COMMON_FILE_OPTIONS: dir_path = tsc.DEPLOY_PATH diff --git a/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py b/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py index 72e3ef911..58cce6a44 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subcloud_group.py @@ -1,5 +1,5 @@ # Copyright (c) 2017 Ericsson AB. -# Copyright (c) 2020-2021 Wind River Systems, Inc. +# Copyright (c) 2020-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -26,6 +26,8 @@ from pecan import expose from pecan import request from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import subcloud_group as subcloud_group_policy +from dcmanager.api import policy from dcmanager.common import consts from dcmanager.common.i18n import _ from dcmanager.common import utils @@ -80,6 +82,8 @@ class SubcloudGroupsController(object): :param group_ref: ID or name of subcloud group """ + policy.authorize(subcloud_group_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if group_ref is None: @@ -144,6 +148,8 @@ class SubcloudGroupsController(object): @index.when(method='POST', template='json') def post(self): """Create a new subcloud group.""" + policy.authorize(subcloud_group_policy.POLICY_ROOT % "create", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() payload = eval(request.body) @@ -192,6 +198,8 @@ class SubcloudGroupsController(object): :param group_ref: ID or name of subcloud group to update """ + policy.authorize(subcloud_group_policy.POLICY_ROOT % "modify", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if group_ref is None: pecan.abort(httpclient.BAD_REQUEST, @@ -256,6 +264,8 @@ class SubcloudGroupsController(object): @index.when(method='delete', template='json') def delete(self, group_ref): """Delete the subcloud group.""" + policy.authorize(subcloud_group_policy.POLICY_ROOT % "delete", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if group_ref is None: diff --git a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py index 1f00c53da..c3a778468 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/subclouds.py +++ b/distributedcloud/dcmanager/api/controllers/v1/subclouds.py @@ -49,6 +49,8 @@ from keystoneauth1 import exceptions as keystone_exceptions import tsconfig.tsconfig as tsc from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import subclouds as subclouds_policy +from dcmanager.api import policy from dcmanager.common import consts from dcmanager.common import exceptions from dcmanager.common.i18n import _ @@ -826,6 +828,8 @@ class SubcloudsController(object): :param subcloud_ref: ID or name of subcloud """ + policy.authorize(subclouds_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if subcloud_ref is None: @@ -947,6 +951,8 @@ class SubcloudsController(object): config) """ + policy.authorize(subclouds_policy.POLICY_ROOT % "create", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if subcloud_ref is None: @@ -1076,6 +1082,8 @@ class SubcloudsController(object): or subcloud update operation """ + policy.authorize(subclouds_policy.POLICY_ROOT % "modify", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() subcloud = None @@ -1468,6 +1476,8 @@ class SubcloudsController(object): :param subcloud_ref: ID or name of subcloud to delete. """ + policy.authorize(subclouds_policy.POLICY_ROOT % "delete", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() subcloud = None diff --git a/distributedcloud/dcmanager/api/controllers/v1/sw_update_options.py b/distributedcloud/dcmanager/api/controllers/v1/sw_update_options.py index e3601827e..49faa163a 100644 --- a/distributedcloud/dcmanager/api/controllers/v1/sw_update_options.py +++ b/distributedcloud/dcmanager/api/controllers/v1/sw_update_options.py @@ -24,6 +24,8 @@ from pecan import request from dccommon import consts as dccommon_consts from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import sw_update_options as sw_update_options_policy +from dcmanager.api import policy from dcmanager.common import exceptions from dcmanager.common.i18n import _ from dcmanager.common import utils @@ -51,6 +53,8 @@ class SwUpdateOptionsController(object): :param subcloud: name or id of subcloud (optional) """ + policy.authorize(sw_update_options_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if subcloud_ref is None: @@ -114,6 +118,8 @@ class SwUpdateOptionsController(object): # Note creating or updating subcloud specific options require # setting all options. + policy.authorize(sw_update_options_policy.POLICY_ROOT % "update", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() payload = eval(request.body) @@ -204,6 +210,8 @@ class SwUpdateOptionsController(object): def delete(self, subcloud_ref): """Delete the software update options.""" + policy.authorize(sw_update_options_policy.POLICY_ROOT % "delete", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() if subcloud_ref == dccommon_consts.DEFAULT_REGION_NAME: diff --git a/distributedcloud/dcmanager/api/controllers/v1/sw_update_strategy.py b/distributedcloud/dcmanager/api/controllers/v1/sw_update_strategy.py index 3337c4adc..c71eaf4ec 100755 --- a/distributedcloud/dcmanager/api/controllers/v1/sw_update_strategy.py +++ b/distributedcloud/dcmanager/api/controllers/v1/sw_update_strategy.py @@ -25,6 +25,8 @@ from pecan import request from dccommon import consts as dccommon_consts from dcmanager.api.controllers import restcomm +from dcmanager.api.policies import sw_update_strategy as sw_update_strat_policy +from dcmanager.api import policy from dcmanager.common import consts from dcmanager.common import exceptions from dcmanager.common.i18n import _ @@ -71,6 +73,8 @@ class SwUpdateStrategyController(object): :param steps: get the steps for this strategy (optional) :param cloud_name: name of cloud (optional) """ + policy.authorize(sw_update_strat_policy.POLICY_ROOT % "get", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() # If 'type' is in the request params, filter the update_type @@ -138,6 +142,9 @@ class SwUpdateStrategyController(object): pecan.abort(400, _('Body required')) if actions is None: + policy.authorize(sw_update_strat_policy.POLICY_ROOT % "create", {}, + restcomm.extract_credentials_for_policy()) + # Validate any options that were supplied strategy_type = payload.get('type') if not strategy_type: @@ -218,6 +225,8 @@ class SwUpdateStrategyController(object): if not action: pecan.abort(400, _('action required')) if action == consts.SW_UPDATE_ACTION_APPLY: + policy.authorize(sw_update_strat_policy.POLICY_ROOT % "apply", + {}, restcomm.extract_credentials_for_policy()) try: # Ask dcmanager-manager to apply the strategy. # It will do all the real work... @@ -230,6 +239,8 @@ class SwUpdateStrategyController(object): LOG.exception(e) pecan.abort(500, _('Unable to apply strategy')) elif action == consts.SW_UPDATE_ACTION_ABORT: + policy.authorize(sw_update_strat_policy.POLICY_ROOT % "abort", + {}, restcomm.extract_credentials_for_policy()) try: # Ask dcmanager-manager to abort the strategy. # It will do all the real work... @@ -245,6 +256,8 @@ class SwUpdateStrategyController(object): @index.when(method='delete', template='json') def delete(self): """Delete the software update strategy.""" + policy.authorize(sw_update_strat_policy.POLICY_ROOT % "delete", {}, + restcomm.extract_credentials_for_policy()) context = restcomm.extract_context_from_environ() # If 'type' is in the request params, filter the update_type diff --git a/distributedcloud/dcmanager/api/enforcer.py b/distributedcloud/dcmanager/api/enforcer.py deleted file mode 100644 index 5dff4cb28..000000000 --- a/distributedcloud/dcmanager/api/enforcer.py +++ /dev/null @@ -1,73 +0,0 @@ -# Copyright 2017 Ericsson AB. -# Copyright (c) 2017, 2019, 2021 Wind River Systems, Inc. -# -# 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 enforcer for DC Manager.""" - -from oslo_config import cfg -from oslo_policy import policy - -from dcmanager.common import exceptions as exc - - -_ENFORCER = None - - -def enforce(action, context, target=None, do_raise=True, - exc=exc.NotAuthorized): - """Verify that the action is valid on the target in this context. - - :param action: String, representing the action to be checked. - This should be colon separated for clarity. - i.e. ``sync:list`` - :param context: DC Manager context. - :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. ``{'project_id': context.project}`` - :param do_raise: if True (the default), raises specified exception. - :param exc: Exception to be raised if not authorized. Default is - dcmanager.common.exceptions.NotAuthorized. - - :return: returns True if authorized and False if not authorized and - do_raise is False. - """ - if cfg.CONF.auth_strategy != 'keystone': - # Policy enforcement is supported now only with Keystone - # authentication. - return - - target_obj = { - 'project_id': context.project, - 'user_id': context.user, - } - - target_obj.update(target or {}) - _ensure_enforcer_initialization() - - try: - _ENFORCER.enforce(action, target_obj, context.to_dict(), - do_raise=do_raise, exc=exc) - return True - - except Exception: - return False - - -def _ensure_enforcer_initialization(): - global _ENFORCER - if not _ENFORCER: - _ENFORCER = policy.Enforcer(cfg.CONF) - _ENFORCER.load_rules() diff --git a/distributedcloud/dcmanager/api/policies/__init__.py b/distributedcloud/dcmanager/api/policies/__init__.py new file mode 100644 index 000000000..d7265e942 --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/__init__.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import itertools + +from dcmanager.api.policies import alarm_manager +from dcmanager.api.policies import base +from dcmanager.api.policies import subcloud_backup +from dcmanager.api.policies import subcloud_deploy +from dcmanager.api.policies import subcloud_group +from dcmanager.api.policies import subclouds +from dcmanager.api.policies import sw_update_options +from dcmanager.api.policies import sw_update_strategy + + +def list_rules(): + return itertools.chain( + base.list_rules(), + subclouds.list_rules(), + subcloud_deploy.list_rules(), + alarm_manager.list_rules(), + sw_update_strategy.list_rules(), + sw_update_options.list_rules(), + subcloud_group.list_rules(), + subcloud_backup.list_rules() + ) diff --git a/distributedcloud/dcmanager/api/policies/alarm_manager.py b/distributedcloud/dcmanager/api/policies/alarm_manager.py new file mode 100644 index 000000000..45247f858 --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/alarm_manager.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:alarm_manager:%s' + + +alarm_manager_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Get alarms from subclouds.", + operations=[ + { + 'method': 'GET', + 'path': '/v1.0/alarms' + } + ] + ) +] + + +def list_rules(): + return alarm_manager_rules diff --git a/distributedcloud/dcmanager/api/policies/base.py b/distributedcloud/dcmanager/api/policies/base.py new file mode 100644 index 000000000..a4455a24a --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/base.py @@ -0,0 +1,30 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from oslo_policy import policy + +ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects' +READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects' + + +base_rules = [ + policy.RuleDefault( + name=ADMIN_IN_SYSTEM_PROJECTS, + check_str='role:admin and (project_name:admin or ' + + 'project_name:services)', + description="Base rule.", + ), + policy.RuleDefault( + name=READER_IN_SYSTEM_PROJECTS, + check_str='role:reader and (project_name:admin or ' + + 'project_name:services)', + description="Base rule." + ) +] + + +def list_rules(): + return base_rules diff --git a/distributedcloud/dcmanager/api/policies/subcloud_backup.py b/distributedcloud/dcmanager/api/policies/subcloud_backup.py new file mode 100644 index 000000000..9949ebc1d --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/subcloud_backup.py @@ -0,0 +1,29 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:subcloud_backup:%s' + + +subcloud_backup_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'create', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Create new subcloud backup.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/subcloud-backup' + } + ] + ) +] + + +def list_rules(): + return subcloud_backup_rules diff --git a/distributedcloud/dcmanager/api/policies/subcloud_deploy.py b/distributedcloud/dcmanager/api/policies/subcloud_deploy.py new file mode 100644 index 000000000..737007a6b --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/subcloud_deploy.py @@ -0,0 +1,40 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:subcloud_deploy:%s' + + +subcloud_deploy_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'upload', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Upload subcloud deploy files.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/subcloud-deploy' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Show subcloud deploy files.", + operations=[ + { + 'method': 'GET', + 'path': '/v1.0/subcloud-deploy' + } + ] + ) +] + + +def list_rules(): + return subcloud_deploy_rules diff --git a/distributedcloud/dcmanager/api/policies/subcloud_group.py b/distributedcloud/dcmanager/api/policies/subcloud_group.py new file mode 100644 index 000000000..b54582ee4 --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/subcloud_group.py @@ -0,0 +1,70 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:subcloud_groups:%s' + + +subcloud_groups_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'create', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Create subcloud group.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/subcloud-groups' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'delete', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Delete subcloud group.", + operations=[ + { + 'method': 'DELETE', + 'path': '/v1.0/subcloud-groups/{subcloud_group}' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Get subcloud groups.", + operations=[ + { + 'method': 'GET', + 'path': '/v1.0/subcloud-groups' + }, + { + 'method': 'GET', + 'path': '/v1.0/subcloud-groups/{subcloud_group}' + }, + { + 'method': 'GET', + 'path': '/v1.0/subcloud-groups/{subcloud_group}/subclouds' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'modify', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Modify subcloud group.", + operations=[ + { + 'method': 'PATCH', + 'path': '/v1.0/subcloud-groups/{subcloud_group}' + } + ] + ) +] + + +def list_rules(): + return subcloud_groups_rules diff --git a/distributedcloud/dcmanager/api/policies/subclouds.py b/distributedcloud/dcmanager/api/policies/subclouds.py new file mode 100644 index 000000000..261876c97 --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/subclouds.py @@ -0,0 +1,90 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:subclouds:%s' + + +subclouds_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'create', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Create a subcloud.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/subclouds' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'delete', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Delete a subcloud.", + operations=[ + { + 'method': 'DELETE', + 'path': '/v1.0/subclouds/{alarm_uuid}' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Get subclouds data.", + operations=[ + { + 'method': 'GET', + 'path': '/v1.0/subclouds' + }, + { + 'method': 'GET', + 'path': '/v1.0/subclouds/{subcloud}' + }, + { + 'method': 'GET', + 'path': '/v1.0/subclouds/{subcloud}/detail' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'modify', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Modify a subcloud.", + operations=[ + { + 'method': 'PATCH', + 'path': '/v1.0/subclouds/{subcloud}' + }, + { + 'method': 'PATCH', + 'path': '/v1.0/subclouds/{subcloud}/prestage' + }, + { + 'method': 'PATCH', + 'path': '/v1.0/subclouds/{subcloud}/reconfigure' + }, + { + 'method': 'PATCH', + 'path': '/v1.0/subclouds/{subcloud}/reinstall' + }, + { + 'method': 'PATCH', + 'path': '/v1.0/subclouds/{subcloud}/restore' + }, + { + 'method': 'PATCH', + 'path': '/v1.0/subclouds/{subcloud}/update_status' + } + ] + ) +] + + +def list_rules(): + return subclouds_rules diff --git a/distributedcloud/dcmanager/api/policies/sw_update_options.py b/distributedcloud/dcmanager/api/policies/sw_update_options.py new file mode 100644 index 000000000..85bd7b064 --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/sw_update_options.py @@ -0,0 +1,55 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:sw_update_options:%s' + + +sw_update_options_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'delete', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Delete per subcloud sw-update options.", + operations=[ + { + 'method': 'DELETE', + 'path': '/v1.0/sw-update-options/{subcloud}' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Get sw-update options.", + operations=[ + { + 'method': 'GET', + 'path': '/v1.0/sw-update-options' + }, + { + 'method': 'GET', + 'path': '/v1.0/sw-update-options/{subcloud}' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'update', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Update sw-update options (defaults or per subcloud).", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/sw-update-options/{subcloud}' + } + ] + ) +] + + +def list_rules(): + return sw_update_options_rules diff --git a/distributedcloud/dcmanager/api/policies/sw_update_strategy.py b/distributedcloud/dcmanager/api/policies/sw_update_strategy.py new file mode 100644 index 000000000..a1391dbcf --- /dev/null +++ b/distributedcloud/dcmanager/api/policies/sw_update_strategy.py @@ -0,0 +1,81 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from dcmanager.api.policies import base +from oslo_policy import policy + +POLICY_ROOT = 'dc_api:sw_update_strategy:%s' + + +sw_update_strategy_rules = [ + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'abort', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Abort update strategy execution.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/sw-update-strategy/actions' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'apply', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Apply update strategy.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/sw-update-strategy/actions' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'create', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Create update strategy.", + operations=[ + { + 'method': 'POST', + 'path': '/v1.0/sw-update-strategy' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'delete', + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, + description="Delete update strategy.", + operations=[ + { + 'method': 'DELETE', + 'path': '/v1.0/sw-update-strategy' + } + ] + ), + policy.DocumentedRuleDefault( + name=POLICY_ROOT % 'get', + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, + description="Get update strategy.", + operations=[ + { + 'method': 'GET', + 'path': '/v1.0/sw-update-strategy' + }, + { + 'method': 'GET', + 'path': '/v1.0/sw-update-strategy/steps' + }, + { + 'method': 'GET', + 'path': '/v1.0/sw-update-strategy/steps/{cloud_name}' + } + ], + ) +] + + +def list_rules(): + return sw_update_strategy_rules diff --git a/distributedcloud/dcmanager/api/policy.py b/distributedcloud/dcmanager/api/policy.py new file mode 100644 index 000000000..919608934 --- /dev/null +++ b/distributedcloud/dcmanager/api/policy.py @@ -0,0 +1,62 @@ +# Copyright (c) 2011 OpenStack Foundation +# 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. +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +"""Policy Engine For DC.""" + +from dcmanager.api import policies as controller_policies +from oslo_config import cfg +from oslo_policy import policy +from webob import exc + + +CONF = cfg.CONF +_ENFORCER = None + + +def reset(): + """Discard current Enforcer object.""" + global _ENFORCER + _ENFORCER = None + + +def init(policy_file='policy.yaml'): + """Init an Enforcer class. + + :param policy_file: Custom policy file to be used. + + :return: Returns a Enforcer instance. + """ + global _ENFORCER + if not _ENFORCER: + + # https://docs.openstack.org/oslo.policy/latest/user/usage.html + _ENFORCER = policy.Enforcer(CONF, + policy_file=policy_file, + default_rule='default', + use_conf=True, + overwrite=True) + _ENFORCER.register_defaults(controller_policies.list_rules()) + return _ENFORCER + + +def authorize(rule, target, creds, do_raise=True): + """A wrapper around 'authorize' from 'oslo_policy.policy'.""" + init() + return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise, + exc=exc.HTTPForbidden) diff --git a/distributedcloud/dcmanager/common/context.py b/distributedcloud/dcmanager/common/context.py index fddb16ef5..ca52dda5e 100644 --- a/distributedcloud/dcmanager/common/context.py +++ b/distributedcloud/dcmanager/common/context.py @@ -1,4 +1,4 @@ -# Copyright (c) 2017, 2019, 2021, 2022 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # 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 @@ -23,7 +23,8 @@ from oslo_log import log from oslo_utils import encodeutils from oslo_utils import uuidutils -from dcmanager.common import policy +from dcmanager.api.policies import base as base_policy +from dcmanager.api import policy from dcmanager.db import api as db_api ALLOWED_WITHOUT_AUTH = '/' @@ -87,9 +88,9 @@ class RequestContext(base_context.RequestContext): # Check user is admin or not if is_admin is None: - self.is_admin = policy.enforce(self, 'context_is_admin', - target={'project': self.project}, - do_raise=False) + self.is_admin = policy.authorize( + base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, self.to_dict(), + do_raise=False) else: self.is_admin = is_admin diff --git a/distributedcloud/dcmanager/common/policy.py b/distributedcloud/dcmanager/common/policy.py deleted file mode 100644 index d01f384c7..000000000 --- a/distributedcloud/dcmanager/common/policy.py +++ /dev/null @@ -1,51 +0,0 @@ -# Copyright (c) 2017, 2019, 2021 Wind River Systems, Inc. -# 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 DC Manager -""" - -# from oslo_concurrency import lockutils -from oslo_config import cfg -from oslo_policy import policy - -from dcmanager.common import exceptions - -POLICY_ENFORCER = None -CONF = cfg.CONF - - -# @lockutils.synchronized('policy_enforcer', 'dcmanager-') -def _get_enforcer(policy_file=None, rules=None, default_rule=None): - - global POLICY_ENFORCER - - if POLICY_ENFORCER is None: - POLICY_ENFORCER = policy.Enforcer(CONF, - policy_file=policy_file, - rules=rules, - default_rule=default_rule) - return POLICY_ENFORCER - - -def enforce(context, rule, target, do_raise=True, *args, **kwargs): - - enforcer = _get_enforcer() - credentials = context.to_dict() - target = target or {} - if do_raise: - kwargs.update(exc=exceptions.Forbidden) - - return enforcer.enforce(rule, target, credentials, do_raise, - *args, **kwargs) diff --git a/distributedcloud/dcmanager/tests/unit/api/test_root_controller.py b/distributedcloud/dcmanager/tests/unit/api/test_root_controller.py index 631677344..ed42461ef 100644 --- a/distributedcloud/dcmanager/tests/unit/api/test_root_controller.py +++ b/distributedcloud/dcmanager/tests/unit/api/test_root_controller.py @@ -1,5 +1,5 @@ # Copyright (c) 2015 Huawei Technologies Co., Ltd. -# Copyright (c) 2017-2021 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -47,7 +47,9 @@ class DCManagerApiTest(base.DCManagerTestCase): api_config.test_init() - self.CONF = self.useFixture(fixture_config.Config()).conf + config = fixture_config.Config() + self.CONF = self.useFixture(config).conf + config.set_config_dirs([]) # self.setup_messaging(self.CONF) self.CONF.set_override('auth_strategy', 'noauth') diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/mixins.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/mixins.py index 14a64e232..eb6130574 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/mixins.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/mixins.py @@ -1,5 +1,5 @@ # Copyright (c) 2017 Ericsson AB -# Copyright (c) 2020-2021 Wind River Systems, Inc. +# Copyright (c) 2020-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -29,8 +29,9 @@ class APIMixin(object): api_headers = { 'X-Tenant-Id': FAKE_TENANT, - 'X_ROLE': 'admin', - 'X-Identity-Status': 'Confirmed' + 'X_ROLE': 'admin,member,reader', + 'X-Identity-Status': 'Confirmed', + 'X-Project-Name': 'admin' } # subclasses should provide methods diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_alarm_manager.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_alarm_manager.py index 9b195d910..13bf1153a 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_alarm_manager.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_alarm_manager.py @@ -1,4 +1,4 @@ -# Copyright (c) 2020-2021 Wind River Systems, Inc. +# Copyright (c) 2020-2022 Wind River Systems, Inc. # 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 @@ -22,8 +22,8 @@ from dcmanager.tests import utils FAKE_URL = '/v1.0/alarms' FAKE_TENANT = utils.UUID1 FAKE_ID = '1' -FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin', - 'X-Identity-Status': 'Confirmed'} +FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader', + 'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'} class TestSubcloudAlarmController(testroot.DCManagerApiTest): diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_notifications.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_notifications.py index 11a3d8765..ec8a0b19c 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_notifications.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_notifications.py @@ -1,4 +1,4 @@ -# Copyright (c) 2021 Wind River Systems, Inc. +# Copyright (c) 2021-2022 Wind River Systems, Inc. # 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 @@ -22,8 +22,8 @@ from dcmanager.tests import utils FAKE_URL = '/v1.0/notifications' FAKE_TENANT = utils.UUID1 -FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin', - 'X-Identity-Status': 'Confirmed'} +FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader', + 'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'} class TestNotificationsController(testroot.DCManagerApiTest): diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py index 9b57806e1..5b0f5231c 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_subcloud_deploy.py @@ -25,8 +25,8 @@ from dcmanager.tests import utils FAKE_TENANT = utils.UUID1 FAKE_ID = '1' FAKE_URL = '/v1.0/subcloud-deploy' -FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin', - 'X-Identity-Status': 'Confirmed'} +FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader', + 'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'} class TestSubcloudDeploy(testroot.DCManagerApiTest): diff --git a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_sw_update_strategy.py b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_sw_update_strategy.py index 9d82d2c7d..1d75e769e 100644 --- a/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_sw_update_strategy.py +++ b/distributedcloud/dcmanager/tests/unit/api/v1/controllers/test_sw_update_strategy.py @@ -1,5 +1,5 @@ # Copyright (c) 2017 Ericsson AB -# Copyright (c) 2017-2021 Wind River Systems, Inc. +# Copyright (c) 2017-2022 Wind River Systems, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -31,8 +31,8 @@ from dcmanager.tests import utils FAKE_TENANT = utils.UUID1 FAKE_ID = '1' FAKE_URL = '/v1.0/sw-update-strategy' -FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin', - 'X-Identity-Status': 'Confirmed'} +FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader', + 'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'} FAKE_SW_UPDATE_DATA = { "type": consts.SW_UPDATE_TYPE_PATCH, diff --git a/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py b/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py index 7a1e0f061..90a2a5231 100644 --- a/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py +++ b/distributedcloud/dcmanager/tests/unit/common/fake_subcloud.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Wind River Systems, Inc. +# Copyright (c) 2020-2022 Wind River Systems, Inc. # # SPDX-License-Identifier: Apache-2.0 # @@ -16,8 +16,8 @@ FAKE_ID = '1' FAKE_URL = '/v1.0/subclouds' WRONG_URL = '/v1.0/wrong' -FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin', - 'X-Identity-Status': 'Confirmed'} +FAKE_HEADERS = {'X-Tenant-Id': FAKE_TENANT, 'X_ROLE': 'admin,member,reader', + 'X-Identity-Status': 'Confirmed', 'X-Project-Name': 'admin'} FAKE_SUBCLOUD_DATA = {"id": FAKE_ID, "name": "subcloud1", diff --git a/distributedcloud/dcorch/api/enforcer.py b/distributedcloud/dcorch/api/enforcer.py deleted file mode 100644 index c2c56a262..000000000 --- a/distributedcloud/dcorch/api/enforcer.py +++ /dev/null @@ -1,71 +0,0 @@ -# Copyright 2017 Ericsson AB. -# -# 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 enforcer for DC Orchestrators.""" - -from oslo_config import cfg -from oslo_policy import policy - -from dcorch.common import exceptions as exc - - -_ENFORCER = None - - -def enforce(action, context, target=None, do_raise=True, - exc=exc.NotAuthorized): - """Verify that the action is valid on the target in this context. - - :param action: String, representing the action to be checked. - This should be colon separated for clarity. - i.e. ``sync:list`` - :param context: dcorch context. - :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. ``{'project_id': context.project}`` - :param do_raise: if True (the default), raises specified exception. - :param exc: Exception to be raised if not authorized. Default is - dcorch.common.exceptions.NotAuthorized. - - :return: returns True if authorized and False if not authorized and - do_raise is False. - """ - if cfg.CONF.auth_strategy != 'keystone': - # Policy enforcement is supported now only with Keystone - # authentication. - return - - target_obj = { - 'project_id': context.project, - 'user_id': context.user, - } - - target_obj.update(target or {}) - _ensure_enforcer_initialization() - - try: - _ENFORCER.enforce(action, target_obj, context.to_dict(), - do_raise=do_raise, exc=exc) - return True - - except Exception: - return False - - -def _ensure_enforcer_initialization(): - global _ENFORCER - if not _ENFORCER: - _ENFORCER = policy.Enforcer(cfg.CONF) - _ENFORCER.load_rules() diff --git a/distributedcloud/dcorch/api/policies/__init__.py b/distributedcloud/dcorch/api/policies/__init__.py new file mode 100644 index 000000000..cfaad9581 --- /dev/null +++ b/distributedcloud/dcorch/api/policies/__init__.py @@ -0,0 +1,15 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +import itertools + +from dcorch.api.policies import base + + +def list_rules(): + return itertools.chain( + base.list_rules() + ) diff --git a/distributedcloud/dcorch/api/policies/base.py b/distributedcloud/dcorch/api/policies/base.py new file mode 100644 index 000000000..a4455a24a --- /dev/null +++ b/distributedcloud/dcorch/api/policies/base.py @@ -0,0 +1,30 @@ +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# + +from oslo_policy import policy + +ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects' +READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects' + + +base_rules = [ + policy.RuleDefault( + name=ADMIN_IN_SYSTEM_PROJECTS, + check_str='role:admin and (project_name:admin or ' + + 'project_name:services)', + description="Base rule.", + ), + policy.RuleDefault( + name=READER_IN_SYSTEM_PROJECTS, + check_str='role:reader and (project_name:admin or ' + + 'project_name:services)', + description="Base rule." + ) +] + + +def list_rules(): + return base_rules diff --git a/distributedcloud/dcorch/api/policy.py b/distributedcloud/dcorch/api/policy.py new file mode 100644 index 000000000..38663f914 --- /dev/null +++ b/distributedcloud/dcorch/api/policy.py @@ -0,0 +1,62 @@ +# Copyright (c) 2011 OpenStack Foundation +# 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. +# +# Copyright (c) 2022 Wind River Systems, Inc. +# +# SPDX-License-Identifier: Apache-2.0 +# +"""Policy Engine For DC.""" + +from dcorch.api import policies as controller_policies +from oslo_config import cfg +from oslo_policy import policy +from webob import exc + + +CONF = cfg.CONF +_ENFORCER = None + + +def reset(): + """Discard current Enforcer object.""" + global _ENFORCER + _ENFORCER = None + + +def init(policy_file='policy.yaml'): + """Init an Enforcer class. + + :param policy_file: Custom policy file to be used. + + :return: Returns a Enforcer instance. + """ + global _ENFORCER + if not _ENFORCER: + + # https://docs.openstack.org/oslo.policy/latest/user/usage.html + _ENFORCER = policy.Enforcer(CONF, + policy_file=policy_file, + default_rule='default', + use_conf=True, + overwrite=True) + _ENFORCER.register_defaults(controller_policies.list_rules()) + return _ENFORCER + + +def authorize(rule, target, creds, do_raise=True): + """A wrapper around 'authorize' from 'oslo_policy.policy'.""" + init() + return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise, + exc=exc.HTTPForbidden) diff --git a/distributedcloud/dcorch/common/context.py b/distributedcloud/dcorch/common/context.py index f05036092..49a4fe0c7 100644 --- a/distributedcloud/dcorch/common/context.py +++ b/distributedcloud/dcorch/common/context.py @@ -17,7 +17,8 @@ from pecan import hooks from oslo_context import context as base_context from oslo_utils import encodeutils -from dcorch.common import policy +from dcorch.api.policies import base as base_policy +from dcorch.api import policy from dcorch.db import api as db_api ALLOWED_WITHOUT_AUTH = '/' @@ -74,9 +75,9 @@ class RequestContext(base_context.RequestContext): # Check user is admin or not if is_admin is None: - self.is_admin = policy.enforce(self, 'context_is_admin', - target={'project': self.project}, - do_raise=False) + self.is_admin = policy.authorize( + base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, self.to_dict(), + do_raise=False) else: self.is_admin = is_admin diff --git a/distributedcloud/dcorch/common/policy.py b/distributedcloud/dcorch/common/policy.py deleted file mode 100644 index 33db3282d..000000000 --- a/distributedcloud/dcorch/common/policy.py +++ /dev/null @@ -1,49 +0,0 @@ -# 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 DC Orchestrators -""" - -# from oslo_concurrency import lockutils -from oslo_config import cfg -from oslo_policy import policy - -from dcorch.common import exceptions - -POLICY_ENFORCER = None -CONF = cfg.CONF - - -# @lockutils.synchronized('policy_enforcer', 'dcorch-') -def _get_enforcer(policy_file=None, rules=None, default_rule=None): - - global POLICY_ENFORCER - - if POLICY_ENFORCER is None: - POLICY_ENFORCER = policy.Enforcer(CONF, - policy_file=policy_file, - rules=rules, - default_rule=default_rule) - return POLICY_ENFORCER - - -def enforce(context, rule, target, do_raise=True, *args, **kwargs): - - enforcer = _get_enforcer() - credentials = context.to_dict() - target = target or {} - if do_raise: - kwargs.update(exc=exceptions.Forbidden) - - return enforcer.enforce(rule, target, credentials, do_raise, - *args, **kwargs)