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 1f0814033..08577d787 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 f8f0b14e2..e8b71dd35 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 2a67e41d8..3bf7c2653 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 39b541e0a..4d47b72bf 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 9577ec104..714586845 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 b560463b2..57921b069 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 94ac3a5b8..000000000 --- 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 000000000..7ee780dba --- /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 84e7e07ba..e2bfb7c0b 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 240b1e5ef..d904e1f6a 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 a7aebf032..b57f788b2 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 c7367fa16..29abfd531 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 ea5a59d60..436008e4f 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 9a4b2a285..13f14dcaf 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 f453bc54a..7df2f1be3 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 2f2acaff5..c4472f918 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() - - -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 _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. + """Discard current Enforcer object.""" + global _ENFORCER + _ENFORCER = None - :return: returns a non-False value (not necessarily "True") if - authorized, and the exact value False if not authorized and - do_raise is False. - """ - 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 init(policy_file='policy.yaml'): + """Init an Enforcer class. + :param policy_file: Custom policy file to be used. -def check_is_admin(context): - """Whether or not role contains 'admin' role according to policy setting. - + :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() - 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 502274805..d8a8b210a 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 5a6d688b4..c3e673538 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 15e19db95..a17e82b36 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 a889e0bb0..000000000 --- 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 000000000..7ee780dba --- /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 884504793..5725a71db 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())))