From 9aee309999c4feb6aa2eaebfc9d139360893ede5 Mon Sep 17 00:00:00 2001 From: Joao Victor Portal Date: Wed, 3 Aug 2022 21:05:35 -0300 Subject: [PATCH] Deprecate old policy engine and restrict access This commit does two different changes: it changes the policy engine to oslo_policy and restrict access to sysinv API to users of projects 'admin' or 'services'. The policy engine deprecated is the one present in the file "sysinv/sysinv/sysinv/sysinv/openstack/common/policy.py" (780 lines). This file is no longer used by this repository and was not deleted because it is used by other repositories, like starlingx/update. The library oslo_policy is used in its place. In fact, the deprecated engine seems to be an ancient version of oslo_policy. The library oslo_policy changed the default format of configuration files from JSON to YAML, so the configuration files named "policy.json" were changed to "policy.yaml". The file that initializes and wraps oslo_policy ("sysinv/sysinv/sysinv/sysinv/common/policy.py") contains the minimal implementation to use this library. The access to sysinv API, before this commit, was restricted to users with role "admin" or "administrator" from any project. This commit restricts the access to users with role "admin" of projects "admin" or "services". This change should not cause problems, because role "administrator" doesn't exist and because all users from Starlingx are from projects "admin" or "services". This change is needed to avoid access from admin users of other projects. To test custom policy rules set in the file "/etc/sysinv/policy.yaml", it will be used the Service Parameter API actions create/apply/modify/ delete/get (commands "system service-parameter-[add/apply/modify/delete/ list]". To test default policy for sysinv API commands, it will be used the command to change the system description (PATCH "/v1/isystems", command "system modify --description='test'"). On test plan, these commands will be reffered as "test commands". Any change in the file "/etc/sysinv/policy.yaml" is detected by policy engine and rules are updated. Test Plan: PASS: Successfully deploy an AIO-SX using an Debian image with this commit present. Successfully create, through openstack CLI, the users: 'testreader' with role 'reader' in project 'admin', 'adminsvc' with role 'admin' in project 'services' and 'otheradmin' with role 'admin' in project 'notadminproject'. Create openrc files for all new users. Note: the other user that will be used is the already existing 'admin' with role 'admin' in project 'admin'. PASS: In the deployed AIO-SX, check the behavior of test commands through different users: for "admin" and "adminsvc" users, all commands are successful; for user "testreader", only "service-parameter-list" command is successful and for user "otheradmin" no command is successful. PASS: In the deployed AIO-SX, add the following lines in file "/etc/sysinv/policy.yaml": config_api:service_parameter:add: role:reader config_api:service_parameter:apply: role:reader config_api:service_parameter:delete: role:reader config_api:service_parameter:get: role:reader config_api:service_parameter:modify: role:reader and check the behavior of test commands through different users: for "admin" and "adminsvc" users, all commands are successful; for users "testreader" and "otheradmin", all commands are successful except the change in the system description ("system modify --description='test'"). PASS: In the deployed AIO-SX, to assert that public API works without authentication, execute the commands: "curl -v http://:6385/v1/" and "curl -v http://:6385/v1/isystems/mgmtvlan" and verify that they are accepted and that the HTTP response is 200, and execute the commands: "curl -v http://:6385/v1/isystems/" and "curl -v http://:6385/v1/service_parameter" and verify that they are rejected and that the HTTP response is 401. PASS: Repeat all tests above changing the deploy to AIO-DX using an CentOS image. PASS: Successfully execute Debian AIO-SX daily regression and sanity tests using an image containing this change. Story: 2010149 Task: 45984 Signed-off-by: Joao Victor Portal Change-Id: Id7aa387e154afb1441a8484b076cdc97f2fc46cb --- devstack/lib/config | 4 +- sysinv/sysinv/centos/sysinv.spec | 3 +- sysinv/sysinv/debian/deb_folder/control | 2 + .../sysinv/debian/deb_folder/sysinv.install | 2 +- sysinv/sysinv/opensuse/sysinv.spec | 3 +- sysinv/sysinv/sysinv/MANIFEST.in | 2 +- sysinv/sysinv/sysinv/etc/sysinv/policy.json | 5 - sysinv/sysinv/sysinv/etc/sysinv/policy.yaml | 11 ++ .../sysinv/etc/sysinv/sysinv.conf.sample | 2 +- sysinv/sysinv/sysinv/requirements.txt | 1 + .../api/controllers/v1/service_parameter.py | 18 +-- sysinv/sysinv/sysinv/sysinv/api/hooks.py | 25 ++- .../sysinv/sysinv/sysinv/api/policies/base.py | 49 ++---- .../sysinv/api/policies/service_parameter.py | 55 +++++-- .../sysinv/sysinv/sysinv/cert_mon/context.py | 7 +- sysinv/sysinv/sysinv/sysinv/common/policy.py | 149 ++++-------------- .../sysinv/sysinv/openstack/common/policy.py | 4 + sysinv/sysinv/sysinv/sysinv/tests/api/base.py | 2 - .../sysinv/sysinv/sysinv/tests/fake_policy.py | 12 +- sysinv/sysinv/sysinv/sysinv/tests/policy.json | 6 - sysinv/sysinv/sysinv/sysinv/tests/policy.yaml | 11 ++ .../sysinv/sysinv/tests/policy_fixture.py | 11 +- 22 files changed, 166 insertions(+), 218 deletions(-) delete mode 100644 sysinv/sysinv/sysinv/etc/sysinv/policy.json create mode 100644 sysinv/sysinv/sysinv/etc/sysinv/policy.yaml delete mode 100644 sysinv/sysinv/sysinv/sysinv/tests/policy.json create mode 100644 sysinv/sysinv/sysinv/sysinv/tests/policy.yaml diff --git a/devstack/lib/config b/devstack/lib/config index 1f0814033f..08577d787b 100644 --- a/devstack/lib/config +++ b/devstack/lib/config @@ -108,7 +108,7 @@ function cleanup_sysinv { pip_uninstall sysinv sudo rm -f $SYSINV_ETC_GOENABLEDD/sysinv_goenabled_check.sh - sudo rm -f $SYSINV_CONF_DIR/policy.json + sudo rm -f $SYSINV_CONF_DIR/policy.yaml sudo rm -f $SYSINV_ETC_MOTDD/10-system sudo rm -f $SYSINV_CONF_DIR/upgrades/delete_load.sh sudo rm -f $STX_OCF_ROOT/resource.d/platform/sysinv-api @@ -247,7 +247,7 @@ function install_sysinv { sudo install -d -m 755 $SYSINV_ETC_GOENABLEDD sudo install -p -D -m 755 $SYSINV_DIR/etc/sysinv/sysinv_goenabled_check.sh $SYSINV_ETC_GOENABLEDD/sysinv_goenabled_check.sh sudo install -d -m 755 $SYSINV_CONF_DIR - sudo install -p -D -m 755 $SYSINV_DIR/etc/sysinv/policy.json $SYSINV_CONF_DIR/policy.json + sudo install -p -D -m 755 $SYSINV_DIR/etc/sysinv/policy.yaml $SYSINV_CONF_DIR/policy.yaml sudo install -d -m 755 $SYSINV_ETC_MOTDD sudo install -p -D -m 755 $SYSINV_DIR/etc/sysinv/motd-system $SYSINV_ETC_MOTDD/10-system sudo install -d -m 755 $SYSINV_CONF_DIR/upgrades diff --git a/sysinv/sysinv/centos/sysinv.spec b/sysinv/sysinv/centos/sysinv.spec index f8f0b14e27..e8b71dd357 100644 --- a/sysinv/sysinv/centos/sysinv.spec +++ b/sysinv/sysinv/centos/sysinv.spec @@ -44,6 +44,7 @@ Requires: python2-oslo-config Requires: python2-oslo-concurrency Requires: python2-oslo-db Requires: python2-oslo-log +Requires: python2-oslo-policy Requires: python2-oslo-rootwrap Requires: python2-oslo-serialization Requires: python2-oslo-service @@ -89,7 +90,7 @@ install -d -m 755 %{buildroot}%{local_etc_goenabledd} install -p -D -m 755 etc/sysinv/sysinv_goenabled_check.sh %{buildroot}%{local_etc_goenabledd}/sysinv_goenabled_check.sh install -d -m 755 %{buildroot}%{local_etc_sysinv} -install -p -D -m 755 etc/sysinv/policy.json %{buildroot}%{local_etc_sysinv}/policy.json +install -p -D -m 755 etc/sysinv/policy.yaml %{buildroot}%{local_etc_sysinv}/policy.yaml install -p -D -m 644 etc/sysinv/crushmap-storage-model.txt %{buildroot}%{local_etc_sysinv}/crushmap-storage-model.txt install -p -D -m 644 etc/sysinv/crushmap-controller-model.txt %{buildroot}%{local_etc_sysinv}/crushmap-controller-model.txt diff --git a/sysinv/sysinv/debian/deb_folder/control b/sysinv/sysinv/debian/deb_folder/control index 2a67e41d84..3bf7c2653a 100644 --- a/sysinv/sysinv/debian/deb_folder/control +++ b/sysinv/sysinv/debian/deb_folder/control @@ -31,6 +31,7 @@ Build-Depends-Indep: python3-oslo.log, python3-oslo.db, python3-oslo.messaging, + python3-oslo.policy, python3-oslo.rootwrap, python3-oslo.service, python3-oslo.utils, @@ -105,6 +106,7 @@ Depends: ${python3:Depends}, ${misc:Depends}, python3-oslo.log, python3-oslo.db, python3-oslo.messaging, + python3-oslo.policy, python3-oslo.rootwrap, python3-oslo.service, python3-oslo.utils, diff --git a/sysinv/sysinv/debian/deb_folder/sysinv.install b/sysinv/sysinv/debian/deb_folder/sysinv.install index 39b541e0a9..4d47b72bf1 100644 --- a/sysinv/sysinv/debian/deb_folder/sysinv.install +++ b/sysinv/sysinv/debian/deb_folder/sysinv.install @@ -1,4 +1,4 @@ -etc/sysinv/policy.json etc/sysinv +etc/sysinv/policy.yaml etc/sysinv etc/sysinv/crushmap-storage-model.txt etc/sysinv etc/sysinv/crushmap-controller-model.txt etc/sysinv etc/sysinv/crushmap-aio-sx.txt etc/sysinv diff --git a/sysinv/sysinv/opensuse/sysinv.spec b/sysinv/sysinv/opensuse/sysinv.spec index 9577ec1042..7145868456 100644 --- a/sysinv/sysinv/opensuse/sysinv.spec +++ b/sysinv/sysinv/opensuse/sysinv.spec @@ -47,6 +47,7 @@ Requires: python2-oslo.config Requires: python2-oslo.concurrency Requires: python2-oslo.db Requires: python2-oslo.log +Requires: python2-oslo.policy Requires: python2-oslo.rootwrap Requires: python2-oslo.serialization Requires: python2-oslo.service @@ -89,7 +90,7 @@ install -d -m 755 %{buildroot}%{local_etc_goenabledd} install -p -D -m 755 etc/sysinv/sysinv_goenabled_check.sh %{buildroot}%{local_etc_goenabledd}/sysinv_goenabled_check.sh install -d -m 755 %{buildroot}%{local_etc_sysinv} -install -p -D -m 644 etc/sysinv/policy.json %{buildroot}%{local_etc_sysinv}/policy.json +install -p -D -m 644 etc/sysinv/policy.yaml %{buildroot}%{local_etc_sysinv}/policy.yaml install -p -D -m 644 etc/sysinv/crushmap-storage-model.txt %{buildroot}%{local_etc_sysinv}/crushmap-storage-model.txt install -p -D -m 644 etc/sysinv/crushmap-controller-model.txt %{buildroot}%{local_etc_sysinv}/crushmap-controller-model.txt diff --git a/sysinv/sysinv/sysinv/MANIFEST.in b/sysinv/sysinv/sysinv/MANIFEST.in index b560463b2f..57921b0694 100644 --- a/sysinv/sysinv/sysinv/MANIFEST.in +++ b/sysinv/sysinv/sysinv/MANIFEST.in @@ -21,5 +21,5 @@ graft doc graft etc include sysinv/db/sqlalchemy/migrate_repo/migrate.cfg include sysinv/openstack/common/config/generator.py -include sysinv/tests/policy.json +include sysinv/tests/policy.yaml graft tools diff --git a/sysinv/sysinv/sysinv/etc/sysinv/policy.json b/sysinv/sysinv/sysinv/etc/sysinv/policy.json deleted file mode 100644 index 94ac3a5b80..0000000000 --- a/sysinv/sysinv/sysinv/etc/sysinv/policy.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "admin": "role:admin or role:administrator", - "admin_api": "is_admin:True", - "default": "rule:admin_api" -} diff --git a/sysinv/sysinv/sysinv/etc/sysinv/policy.yaml b/sysinv/sysinv/sysinv/etc/sysinv/policy.yaml new file mode 100644 index 0000000000..7ee780dba6 --- /dev/null +++ b/sysinv/sysinv/sysinv/etc/sysinv/policy.yaml @@ -0,0 +1,11 @@ +--- +# The commented lines below contains the default values for presented rules. + +# admin_in_system_projects: role:admin and (project_name:admin or project_name:services) +# reader_in_system_projects: role:reader and (project_name:admin or project_name:services) + +# config_api:service_parameter:add: rule:admin_in_system_projects +# config_api:service_parameter:apply: rule:admin_in_system_projects +# config_api:service_parameter:delete: rule:admin_in_system_projects +# config_api:service_parameter:get: rule:reader_in_system_projects +# config_api:service_parameter:modify: rule:admin_in_system_projects diff --git a/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf.sample b/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf.sample index 84e7e07bab..e2bfb7c0ba 100644 --- a/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf.sample +++ b/sysinv/sysinv/sysinv/etc/sysinv/sysinv.conf.sample @@ -59,7 +59,7 @@ # # JSON file representing policy (string value) -#policy_file=policy.json +#policy_file=policy.yaml # Rule checked when requested rule is not found (string value) #policy_default_rule=default diff --git a/sysinv/sysinv/sysinv/requirements.txt b/sysinv/sysinv/sysinv/requirements.txt index 240b1e5eff..d904e1f6a2 100644 --- a/sysinv/sysinv/sysinv/requirements.txt +++ b/sysinv/sysinv/sysinv/requirements.txt @@ -52,3 +52,4 @@ python-barbicanclient rfc3986 importlib-metadata>=3.3.0;python_version=="3.6" importlib-resources==5.2.2;python_version=="3.6" +oslo.policy # Apache-2.0 \ No newline at end of file diff --git a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/service_parameter.py b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/service_parameter.py index a7aebf0324..b57f788b20 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/service_parameter.py +++ b/sysinv/sysinv/sysinv/sysinv/api/controllers/v1/service_parameter.py @@ -27,7 +27,6 @@ from sysinv.api.controllers.v1 import link from sysinv.api.controllers.v1 import types from sysinv.api.controllers.v1 import utils from sysinv.api.controllers.v1.query import Query -from sysinv.api.policies import base as base_policy from sysinv.api.policies import service_parameter as sp_policy from sysinv import objects from sysinv.common import constants @@ -827,21 +826,16 @@ class ServiceParameterController(rest.RestController): def enforce_policy(self, method_name, request): """Check policy rules for each action of this controller.""" - context = request.context + context_dict = request.context.to_dict() if method_name == "apply": - policy.enforce(context, sp_policy.POLICY_ROOT % "apply", - {'project_name': base_policy.ADMIN_PROJECT_NAME}) + policy.authorize(sp_policy.POLICY_ROOT % "apply", {}, context_dict) elif method_name == "delete": - policy.enforce(context, sp_policy.POLICY_ROOT % "delete", - {'project_name': base_policy.ADMIN_PROJECT_NAME}) + policy.authorize(sp_policy.POLICY_ROOT % "delete", {}, context_dict) elif method_name in ["get_all", "get_one"]: - policy.enforce(context, sp_policy.POLICY_ROOT % "get", - {'project_name': base_policy.ADMIN_PROJECT_NAME}) + policy.authorize(sp_policy.POLICY_ROOT % "get", {}, context_dict) elif method_name == "patch": - policy.enforce(context, sp_policy.POLICY_ROOT % "modify", - {'project_name': base_policy.ADMIN_PROJECT_NAME}) + policy.authorize(sp_policy.POLICY_ROOT % "modify", {}, context_dict) elif method_name == "post": - policy.enforce(context, sp_policy.POLICY_ROOT % "add", - {'project_name': base_policy.ADMIN_PROJECT_NAME}) + policy.authorize(sp_policy.POLICY_ROOT % "add", {}, context_dict) else: raise exception.PolicyNotFound() diff --git a/sysinv/sysinv/sysinv/sysinv/api/hooks.py b/sysinv/sysinv/sysinv/sysinv/api/hooks.py index c7367fa161..29abfd531d 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/hooks.py +++ b/sysinv/sysinv/sysinv/sysinv/api/hooks.py @@ -31,11 +31,12 @@ from oslo_utils import uuidutils from pecan import hooks from sysinv._i18n import _ +from sysinv.api.policies import base as base_policy from sysinv.common import context from sysinv.common import utils from sysinv.conductor import rpcapi from sysinv.db import api as dbapi -from sysinv.openstack.common import policy +from sysinv.common import policy from webob import exc LOG = log.getLogger(__name__) @@ -141,7 +142,7 @@ class ContextHook(hooks.PecanHook): domain_id = state.request.headers.get('X-User-Domain-Id') domain_name = state.request.headers.get('X-User-Domain-Name') auth_token = state.request.headers.get('X-Auth-Token', None) - creds = {'roles': state.request.headers.get('X-Roles', '').split(',')} + roles = state.request.headers.get('X-Roles', '').split(',') catalog_header = state.request.headers.get('X-Service-Catalog') service_catalog = None if catalog_header: @@ -151,7 +152,12 @@ class ContextHook(hooks.PecanHook): raise webob.exc.HTTPInternalServerError( _('Invalid service catalog json.')) - is_admin = policy.check('admin', state.request.headers, creds) + credentials = { + 'project_name': project_name, + 'roles': roles + } + is_admin = policy.authorize(base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, + credentials, do_raise=False) utils.safe_rstrip(state.request.path, '/') is_public_api = state.request.environ.get('is_public_api', False) @@ -165,7 +171,7 @@ class ContextHook(hooks.PecanHook): is_admin=is_admin, is_public_api=is_public_api, project_name=project_name, - roles=creds['roles'], + roles=roles, service_catalog=service_catalog ) @@ -182,11 +188,16 @@ class AccessPolicyHook(hooks.PecanHook): def before(self, state): controller = state.controller.__self__ if hasattr(controller, 'enforce_policy'): - controller_method = state.controller.__name__ - controller.enforce_policy(controller_method, state.request) + try: + controller_method = state.controller.__name__ + controller.enforce_policy(controller_method, state.request) + except Exception: + raise exc.HTTPForbidden() else: context = state.request.context - is_admin_api = policy.check('admin_api', {}, context.to_dict()) + is_admin_api = policy.authorize( + base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, + context.to_dict(), do_raise=False) if not is_admin_api and not context.is_public_api: raise exc.HTTPForbidden() diff --git a/sysinv/sysinv/sysinv/sysinv/api/policies/base.py b/sysinv/sysinv/sysinv/sysinv/api/policies/base.py index ea5a59d608..436008e4f3 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/policies/base.py +++ b/sysinv/sysinv/sysinv/sysinv/api/policies/base.py @@ -14,49 +14,24 @@ # # SPDX-License-Identifier: Apache-2.0 -ADMIN_PROJECT_NAME = 'admin' -ADMIN_IN_SPECIFIC_PROJECT = 'rule:admin_in_specific_project' -READER_IN_SPECIFIC_PROJECT = 'rule:reader_in_specific_project' +from oslo_policy import policy - -class RuleDefault(object): - """Class used to represent a policy rule. - - :param name: The name of the policy. - :param check_str: The string that represents the policy. - :param description: A brief description of the policy. - """ - def __init__(self, name, check_str, description): - self.name = name - self.check_str = check_str - self.description = description +ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects' +READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects' base_rules = [ - RuleDefault( - name='admin', - check_str='role:admin or role:administrator', + policy.RuleDefault( + name=ADMIN_IN_SYSTEM_PROJECTS, + check_str='role:admin and (project_name:admin or ' + + 'project_name:services)', description="Base rule.", ), - RuleDefault( - name='admin_api', - check_str='is_admin:True', - description="Base rule.", - ), - RuleDefault( - name='default', - check_str='rule:admin_api', - description="Base rule.", - ), - RuleDefault( - name='admin_in_specific_project', - check_str='role:admin and project_name:%(project_name)s', - description="Base rule.", - ), - RuleDefault( - name='reader_in_specific_project', - check_str='role:reader and project_name:%(project_name)s', - 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." ) ] diff --git a/sysinv/sysinv/sysinv/sysinv/api/policies/service_parameter.py b/sysinv/sysinv/sysinv/sysinv/api/policies/service_parameter.py index 9a4b2a285a..13f14dcaf5 100644 --- a/sysinv/sysinv/sysinv/sysinv/api/policies/service_parameter.py +++ b/sysinv/sysinv/sysinv/sysinv/api/policies/service_parameter.py @@ -14,36 +14,71 @@ # # SPDX-License-Identifier: Apache-2.0 +from oslo_policy import policy from sysinv.api.policies import base POLICY_ROOT = 'config_api:service_parameter:%s' service_parameter_rules = [ - base.RuleDefault( + policy.DocumentedRuleDefault( name=POLICY_ROOT % 'add', - check_str=base.ADMIN_IN_SPECIFIC_PROJECT, + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, description="Add a Service Parameter.", + operations=[ + { + 'method': 'POST', + 'path': '/v1/service_parameter' + } + ] ), - base.RuleDefault( + policy.DocumentedRuleDefault( name=POLICY_ROOT % 'apply', - check_str=base.ADMIN_IN_SPECIFIC_PROJECT, + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, description="Apply Service Parameters.", + operations=[ + { + 'method': 'POST', + 'path': '/v1/service_parameter/apply' + } + ] ), - base.RuleDefault( + policy.DocumentedRuleDefault( name=POLICY_ROOT % 'delete', - check_str=base.ADMIN_IN_SPECIFIC_PROJECT, + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, description="Delete a Service Parameter.", + operations=[ + { + 'method': 'DELETE', + 'path': '/v1/service_parameter/{parameter_id}' + } + ] ), - base.RuleDefault( + policy.DocumentedRuleDefault( name=POLICY_ROOT % 'get', - check_str=base.READER_IN_SPECIFIC_PROJECT, + check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS, description="Get Service Parameters.", + operations=[ + { + 'method': 'GET', + 'path': '/v1/service_parameter' + }, + { + 'method': 'GET', + 'path': '/v1/service_parameter/{parameter_id}' + } + ] ), - base.RuleDefault( + policy.DocumentedRuleDefault( name=POLICY_ROOT % 'modify', - check_str=base.ADMIN_IN_SPECIFIC_PROJECT, + check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS, description="Modify Service Parameter value.", + operations=[ + { + 'method': 'PATCH', + 'path': '/v1/service_parameter/{parameter_id}' + } + ] ) ] diff --git a/sysinv/sysinv/sysinv/sysinv/cert_mon/context.py b/sysinv/sysinv/sysinv/sysinv/cert_mon/context.py index f453bc54a3..7df2f1be3d 100644 --- a/sysinv/sysinv/sysinv/sysinv/cert_mon/context.py +++ b/sysinv/sysinv/sysinv/sysinv/cert_mon/context.py @@ -20,6 +20,7 @@ from pecan import hooks from oslo_context import context as base_context from oslo_utils import encodeutils +from sysinv.api.policies import base as base_policy from sysinv.common import policy ALLOWED_WITHOUT_AUTH = '/' @@ -75,9 +76,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/sysinv/sysinv/sysinv/sysinv/common/policy.py b/sysinv/sysinv/sysinv/sysinv/common/policy.py index 2f2acaff5d..c4472f918a 100644 --- a/sysinv/sysinv/sysinv/sysinv/common/policy.py +++ b/sysinv/sysinv/sysinv/sysinv/common/policy.py @@ -1,134 +1,55 @@ -# vim: tabstop=4 shiftwidth=4 softtabstop=4 - -# Copyright (c) 2011 OpenStack Foundation -# All Rights Reserved. +# Copyright (c) 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 +# 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. - +# 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. +# +# SPDX-License-Identifier: Apache-2.0 """Policy Engine For Sysinv.""" -import os.path - from oslo_config import cfg -from sysinv._i18n import _ +from oslo_policy import policy from sysinv.api import policies as controller_policies -from sysinv.common import exception -from sysinv.common import utils -from sysinv.openstack.common import policy -policy_opts = [ - cfg.StrOpt('policy_file', - default='policy.json', - help=_('JSON file representing policy')), - cfg.StrOpt('policy_default_rule', - default='default', - help=_('Rule checked when requested rule is not found')), - ] - CONF = cfg.CONF -CONF.register_opts(policy_opts) - -_POLICY_PATH = None -_POLICY_CACHE = {} +_ENFORCER = None def reset(): - global _POLICY_PATH - global _POLICY_CACHE - _POLICY_PATH = None - _POLICY_CACHE = {} - policy.reset() + """Discard current Enforcer object.""" + global _ENFORCER + _ENFORCER = None -def init(): - global _POLICY_PATH - global _POLICY_CACHE - if not _POLICY_PATH: - _POLICY_PATH = CONF.policy_file - if not os.path.exists(_POLICY_PATH): - _POLICY_PATH = CONF.find_file(_POLICY_PATH) - if not _POLICY_PATH: - raise exception.ConfigNotFound(message=CONF.policy_file) - utils.read_cached_file(_POLICY_PATH, _POLICY_CACHE, - reload_func=_set_rules) +def init(policy_file='policy.yaml'): + """Init an Enforcer class. + :param policy_file: Custom policy file to be used. -def _set_rules(data): - default_rule = CONF.policy_default_rule - rules = policy.Rules.load_rules(data, default_rule, - controller_policies.list_rules()) - policy.set_rules(rules) - - -def enforce(context, action, target, do_raise=True): - """Verifies that the action is valid on the target in this context. - - :param context: sysinv context - :param action: string representing the action to be checked - this should be colon separated for clarity. - i.e. ``compute:create_instance``, - ``compute:attach_volume``, - ``volume:attach_volume`` - :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_id}`` - :param do_raise: if True (the default), raises PolicyNotAuthorized; - if False, returns False - - :raises sysinv.exception.PolicyNotAuthorized: if verification fails - and do_raise is True. - - :return: returns a non-False value (not necessarily "True") if - authorized, and the exact value False if not authorized and - do_raise is False. + :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() - - credentials = context.to_dict() - - # Add the exception arguments if asked to do a raise - extra = {} - if do_raise: - extra.update(exc=exception.PolicyNotAuthorized, action=action) - - return policy.check(action, target, credentials, **extra) - - -def check_is_admin(context): - """Whether or not role contains 'admin' role according to policy setting. - - """ - init() - - credentials = context.to_dict() - target = credentials - - return policy.check('context_is_admin', target, credentials) - - -@policy.register('context_is_admin') -class IsAdminCheck(policy.Check): - """An explicit check for is_admin.""" - - def __init__(self, kind, match): - """Initialize the check.""" - - self.expected = (match.lower() == 'true') - - super(IsAdminCheck, self).__init__(kind, str(self.expected)) - - def __call__(self, target, creds): - """Determine whether is_admin matches the requested value.""" - - return creds['is_admin'] == self.expected + return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise) diff --git a/sysinv/sysinv/sysinv/sysinv/openstack/common/policy.py b/sysinv/sysinv/sysinv/sysinv/openstack/common/policy.py index 5022748055..d8a8b210a2 100644 --- a/sysinv/sysinv/sysinv/sysinv/openstack/common/policy.py +++ b/sysinv/sysinv/sysinv/sysinv/openstack/common/policy.py @@ -16,6 +16,10 @@ # under the License. """ +IMPORTANT: this implementation was deprecated by oslo_policy and it is no longer +used in this repository. This file was not deleted because it is used by other +repositories. + Common Policy Engine Implementation Policies can be expressed in one of two forms: A list of lists, or a diff --git a/sysinv/sysinv/sysinv/sysinv/tests/api/base.py b/sysinv/sysinv/sysinv/sysinv/tests/api/base.py index 5a6d688b42..c3e673538e 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/api/base.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/api/base.py @@ -47,8 +47,6 @@ class FunctionalTest(base.TestCase): def setUp(self): super(FunctionalTest, self).setUp() cfg.CONF.set_override("auth_version", "v2.0", group=acl.OPT_GROUP_NAME) - cfg.CONF.set_override("policy_file", - self.path_get('tests/policy.json')) self.app = self._make_app() self.dbapi = dbapi.get_instance() self.context = sysinv_context.RequestContext(is_admin=True) diff --git a/sysinv/sysinv/sysinv/sysinv/tests/fake_policy.py b/sysinv/sysinv/sysinv/sysinv/tests/fake_policy.py index 15e19db95d..a17e82b36e 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/fake_policy.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/fake_policy.py @@ -16,10 +16,10 @@ policy_data = """ -{ - "admin_api": "role:admin", - "admin_or_owner": "is_admin:True or project_id:%(project_id)s", - "is_admin": "role:admin or role:administrator", - "default": "rule:admin_or_owner" -} +--- +admin_api: "role:admin" +admin_or_owner: "is_admin:True or project_id:%(project_id)s" +is_admin: "role:admin or role:administrator" +default: "rule:admin_or_owner" + """ diff --git a/sysinv/sysinv/sysinv/sysinv/tests/policy.json b/sysinv/sysinv/sysinv/sysinv/tests/policy.json deleted file mode 100644 index a889e0bb0f..0000000000 --- a/sysinv/sysinv/sysinv/sysinv/tests/policy.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "admin_api": "is_admin:True", - "admin_or_owner": "is_admin:True or project_id:%(project_id)s", - "is_admin": "role:admin or role:administrator", - "default": "rule:admin_or_owner", -} diff --git a/sysinv/sysinv/sysinv/sysinv/tests/policy.yaml b/sysinv/sysinv/sysinv/sysinv/tests/policy.yaml new file mode 100644 index 0000000000..7ee780dba6 --- /dev/null +++ b/sysinv/sysinv/sysinv/sysinv/tests/policy.yaml @@ -0,0 +1,11 @@ +--- +# The commented lines below contains the default values for presented rules. + +# admin_in_system_projects: role:admin and (project_name:admin or project_name:services) +# reader_in_system_projects: role:reader and (project_name:admin or project_name:services) + +# config_api:service_parameter:add: rule:admin_in_system_projects +# config_api:service_parameter:apply: rule:admin_in_system_projects +# config_api:service_parameter:delete: rule:admin_in_system_projects +# config_api:service_parameter:get: rule:reader_in_system_projects +# config_api:service_parameter:modify: rule:admin_in_system_projects diff --git a/sysinv/sysinv/sysinv/sysinv/tests/policy_fixture.py b/sysinv/sysinv/sysinv/sysinv/tests/policy_fixture.py index 8845047933..5725a71db8 100644 --- a/sysinv/sysinv/sysinv/sysinv/tests/policy_fixture.py +++ b/sysinv/sysinv/sysinv/sysinv/tests/policy_fixture.py @@ -18,7 +18,6 @@ import fixtures from oslo_config import cfg from sysinv.common import policy as sysinv_policy -from sysinv.openstack.common import policy as common_policy from sysinv.tests import fake_policy CONF = cfg.CONF @@ -30,15 +29,9 @@ class PolicyFixture(fixtures.Fixture): super(PolicyFixture, self).setUp() self.policy_dir = self.useFixture(fixtures.TempDir()) self.policy_file_name = os.path.join(self.policy_dir.path, - 'policy.json') + 'policy.yaml') with open(self.policy_file_name, 'w') as policy_file: policy_file.write(fake_policy.policy_data) - CONF.set_override('policy_file', self.policy_file_name) sysinv_policy.reset() - sysinv_policy.init() + sysinv_policy.init(self.policy_file_name) self.addCleanup(sysinv_policy.reset) - - def set_rules(self, rules): - common_policy.set_rules(common_policy.Rules( - dict((k, common_policy.parse_rule(v)) - for k, v in rules.items())))