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://<MGMT_IP>:6385/v1/" and
"curl -v http://<MGMT_IP>:6385/v1/isystems/mgmtvlan" and
verify that they are accepted and that the HTTP response is 200,
and execute the commands:
"curl -v http://<MGMT_IP>:6385/v1/isystems/" and
"curl -v http://<MGMT_IP>: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 <Joao.VictorPortal@windriver.com>
Change-Id: Id7aa387e154afb1441a8484b076cdc97f2fc46cb
This commit is contained in:
Joao Victor Portal 2022-08-03 21:05:35 -03:00
parent 986c61d0f8
commit 9aee309999
22 changed files with 166 additions and 218 deletions

View File

@ -108,7 +108,7 @@ function cleanup_sysinv {
pip_uninstall sysinv pip_uninstall sysinv
sudo rm -f $SYSINV_ETC_GOENABLEDD/sysinv_goenabled_check.sh 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_ETC_MOTDD/10-system
sudo rm -f $SYSINV_CONF_DIR/upgrades/delete_load.sh sudo rm -f $SYSINV_CONF_DIR/upgrades/delete_load.sh
sudo rm -f $STX_OCF_ROOT/resource.d/platform/sysinv-api 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 -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 -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 -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 -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 -p -D -m 755 $SYSINV_DIR/etc/sysinv/motd-system $SYSINV_ETC_MOTDD/10-system
sudo install -d -m 755 $SYSINV_CONF_DIR/upgrades sudo install -d -m 755 $SYSINV_CONF_DIR/upgrades

View File

@ -44,6 +44,7 @@ Requires: python2-oslo-config
Requires: python2-oslo-concurrency Requires: python2-oslo-concurrency
Requires: python2-oslo-db Requires: python2-oslo-db
Requires: python2-oslo-log Requires: python2-oslo-log
Requires: python2-oslo-policy
Requires: python2-oslo-rootwrap Requires: python2-oslo-rootwrap
Requires: python2-oslo-serialization Requires: python2-oslo-serialization
Requires: python2-oslo-service 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 -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 -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-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 install -p -D -m 644 etc/sysinv/crushmap-controller-model.txt %{buildroot}%{local_etc_sysinv}/crushmap-controller-model.txt

View File

@ -31,6 +31,7 @@ Build-Depends-Indep:
python3-oslo.log, python3-oslo.log,
python3-oslo.db, python3-oslo.db,
python3-oslo.messaging, python3-oslo.messaging,
python3-oslo.policy,
python3-oslo.rootwrap, python3-oslo.rootwrap,
python3-oslo.service, python3-oslo.service,
python3-oslo.utils, python3-oslo.utils,
@ -105,6 +106,7 @@ Depends: ${python3:Depends}, ${misc:Depends},
python3-oslo.log, python3-oslo.log,
python3-oslo.db, python3-oslo.db,
python3-oslo.messaging, python3-oslo.messaging,
python3-oslo.policy,
python3-oslo.rootwrap, python3-oslo.rootwrap,
python3-oslo.service, python3-oslo.service,
python3-oslo.utils, python3-oslo.utils,

View File

@ -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-storage-model.txt etc/sysinv
etc/sysinv/crushmap-controller-model.txt etc/sysinv etc/sysinv/crushmap-controller-model.txt etc/sysinv
etc/sysinv/crushmap-aio-sx.txt etc/sysinv etc/sysinv/crushmap-aio-sx.txt etc/sysinv

View File

@ -47,6 +47,7 @@ Requires: python2-oslo.config
Requires: python2-oslo.concurrency Requires: python2-oslo.concurrency
Requires: python2-oslo.db Requires: python2-oslo.db
Requires: python2-oslo.log Requires: python2-oslo.log
Requires: python2-oslo.policy
Requires: python2-oslo.rootwrap Requires: python2-oslo.rootwrap
Requires: python2-oslo.serialization Requires: python2-oslo.serialization
Requires: python2-oslo.service 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 -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 -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-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 install -p -D -m 644 etc/sysinv/crushmap-controller-model.txt %{buildroot}%{local_etc_sysinv}/crushmap-controller-model.txt

View File

@ -21,5 +21,5 @@ graft doc
graft etc graft etc
include sysinv/db/sqlalchemy/migrate_repo/migrate.cfg include sysinv/db/sqlalchemy/migrate_repo/migrate.cfg
include sysinv/openstack/common/config/generator.py include sysinv/openstack/common/config/generator.py
include sysinv/tests/policy.json include sysinv/tests/policy.yaml
graft tools graft tools

View File

@ -1,5 +0,0 @@
{
"admin": "role:admin or role:administrator",
"admin_api": "is_admin:True",
"default": "rule:admin_api"
}

View File

@ -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

View File

@ -59,7 +59,7 @@
# #
# JSON file representing policy (string value) # JSON file representing policy (string value)
#policy_file=policy.json #policy_file=policy.yaml
# Rule checked when requested rule is not found (string value) # Rule checked when requested rule is not found (string value)
#policy_default_rule=default #policy_default_rule=default

View File

@ -52,3 +52,4 @@ python-barbicanclient
rfc3986 rfc3986
importlib-metadata>=3.3.0;python_version=="3.6" importlib-metadata>=3.3.0;python_version=="3.6"
importlib-resources==5.2.2;python_version=="3.6" importlib-resources==5.2.2;python_version=="3.6"
oslo.policy # Apache-2.0

View File

@ -27,7 +27,6 @@ from sysinv.api.controllers.v1 import link
from sysinv.api.controllers.v1 import types from sysinv.api.controllers.v1 import types
from sysinv.api.controllers.v1 import utils from sysinv.api.controllers.v1 import utils
from sysinv.api.controllers.v1.query import Query 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.api.policies import service_parameter as sp_policy
from sysinv import objects from sysinv import objects
from sysinv.common import constants from sysinv.common import constants
@ -827,21 +826,16 @@ class ServiceParameterController(rest.RestController):
def enforce_policy(self, method_name, request): def enforce_policy(self, method_name, request):
"""Check policy rules for each action of this controller.""" """Check policy rules for each action of this controller."""
context = request.context context_dict = request.context.to_dict()
if method_name == "apply": if method_name == "apply":
policy.enforce(context, sp_policy.POLICY_ROOT % "apply", policy.authorize(sp_policy.POLICY_ROOT % "apply", {}, context_dict)
{'project_name': base_policy.ADMIN_PROJECT_NAME})
elif method_name == "delete": elif method_name == "delete":
policy.enforce(context, sp_policy.POLICY_ROOT % "delete", policy.authorize(sp_policy.POLICY_ROOT % "delete", {}, context_dict)
{'project_name': base_policy.ADMIN_PROJECT_NAME})
elif method_name in ["get_all", "get_one"]: elif method_name in ["get_all", "get_one"]:
policy.enforce(context, sp_policy.POLICY_ROOT % "get", policy.authorize(sp_policy.POLICY_ROOT % "get", {}, context_dict)
{'project_name': base_policy.ADMIN_PROJECT_NAME})
elif method_name == "patch": elif method_name == "patch":
policy.enforce(context, sp_policy.POLICY_ROOT % "modify", policy.authorize(sp_policy.POLICY_ROOT % "modify", {}, context_dict)
{'project_name': base_policy.ADMIN_PROJECT_NAME})
elif method_name == "post": elif method_name == "post":
policy.enforce(context, sp_policy.POLICY_ROOT % "add", policy.authorize(sp_policy.POLICY_ROOT % "add", {}, context_dict)
{'project_name': base_policy.ADMIN_PROJECT_NAME})
else: else:
raise exception.PolicyNotFound() raise exception.PolicyNotFound()

View File

@ -31,11 +31,12 @@ from oslo_utils import uuidutils
from pecan import hooks from pecan import hooks
from sysinv._i18n import _ from sysinv._i18n import _
from sysinv.api.policies import base as base_policy
from sysinv.common import context from sysinv.common import context
from sysinv.common import utils from sysinv.common import utils
from sysinv.conductor import rpcapi from sysinv.conductor import rpcapi
from sysinv.db import api as dbapi from sysinv.db import api as dbapi
from sysinv.openstack.common import policy from sysinv.common import policy
from webob import exc from webob import exc
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -141,7 +142,7 @@ class ContextHook(hooks.PecanHook):
domain_id = state.request.headers.get('X-User-Domain-Id') domain_id = state.request.headers.get('X-User-Domain-Id')
domain_name = state.request.headers.get('X-User-Domain-Name') domain_name = state.request.headers.get('X-User-Domain-Name')
auth_token = state.request.headers.get('X-Auth-Token', None) 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') catalog_header = state.request.headers.get('X-Service-Catalog')
service_catalog = None service_catalog = None
if catalog_header: if catalog_header:
@ -151,7 +152,12 @@ class ContextHook(hooks.PecanHook):
raise webob.exc.HTTPInternalServerError( raise webob.exc.HTTPInternalServerError(
_('Invalid service catalog json.')) _('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, '/') utils.safe_rstrip(state.request.path, '/')
is_public_api = state.request.environ.get('is_public_api', False) is_public_api = state.request.environ.get('is_public_api', False)
@ -165,7 +171,7 @@ class ContextHook(hooks.PecanHook):
is_admin=is_admin, is_admin=is_admin,
is_public_api=is_public_api, is_public_api=is_public_api,
project_name=project_name, project_name=project_name,
roles=creds['roles'], roles=roles,
service_catalog=service_catalog service_catalog=service_catalog
) )
@ -182,11 +188,16 @@ class AccessPolicyHook(hooks.PecanHook):
def before(self, state): def before(self, state):
controller = state.controller.__self__ controller = state.controller.__self__
if hasattr(controller, 'enforce_policy'): if hasattr(controller, 'enforce_policy'):
controller_method = state.controller.__name__ try:
controller.enforce_policy(controller_method, state.request) controller_method = state.controller.__name__
controller.enforce_policy(controller_method, state.request)
except Exception:
raise exc.HTTPForbidden()
else: else:
context = state.request.context 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: if not is_admin_api and not context.is_public_api:
raise exc.HTTPForbidden() raise exc.HTTPForbidden()

View File

@ -14,49 +14,24 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
ADMIN_PROJECT_NAME = 'admin' from oslo_policy import policy
ADMIN_IN_SPECIFIC_PROJECT = 'rule:admin_in_specific_project'
READER_IN_SPECIFIC_PROJECT = 'rule:reader_in_specific_project'
ADMIN_IN_SYSTEM_PROJECTS = 'admin_in_system_projects'
class RuleDefault(object): READER_IN_SYSTEM_PROJECTS = 'reader_in_system_projects'
"""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
base_rules = [ base_rules = [
RuleDefault( policy.RuleDefault(
name='admin', name=ADMIN_IN_SYSTEM_PROJECTS,
check_str='role:admin or role:administrator', check_str='role:admin and (project_name:admin or ' +
'project_name:services)',
description="Base rule.", description="Base rule.",
), ),
RuleDefault( policy.RuleDefault(
name='admin_api', name=READER_IN_SYSTEM_PROJECTS,
check_str='is_admin:True', check_str='role:reader and (project_name:admin or ' +
description="Base rule.", 'project_name:services)',
), 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.",
) )
] ]

View File

@ -14,36 +14,71 @@
# #
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
from oslo_policy import policy
from sysinv.api.policies import base from sysinv.api.policies import base
POLICY_ROOT = 'config_api:service_parameter:%s' POLICY_ROOT = 'config_api:service_parameter:%s'
service_parameter_rules = [ service_parameter_rules = [
base.RuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'add', name=POLICY_ROOT % 'add',
check_str=base.ADMIN_IN_SPECIFIC_PROJECT, check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Add a Service Parameter.", description="Add a Service Parameter.",
operations=[
{
'method': 'POST',
'path': '/v1/service_parameter'
}
]
), ),
base.RuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'apply', name=POLICY_ROOT % 'apply',
check_str=base.ADMIN_IN_SPECIFIC_PROJECT, check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Apply Service Parameters.", description="Apply Service Parameters.",
operations=[
{
'method': 'POST',
'path': '/v1/service_parameter/apply'
}
]
), ),
base.RuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete', name=POLICY_ROOT % 'delete',
check_str=base.ADMIN_IN_SPECIFIC_PROJECT, check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Delete a Service Parameter.", description="Delete a Service Parameter.",
operations=[
{
'method': 'DELETE',
'path': '/v1/service_parameter/{parameter_id}'
}
]
), ),
base.RuleDefault( policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get', name=POLICY_ROOT % 'get',
check_str=base.READER_IN_SPECIFIC_PROJECT, check_str='rule:' + base.READER_IN_SYSTEM_PROJECTS,
description="Get Service Parameters.", 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', name=POLICY_ROOT % 'modify',
check_str=base.ADMIN_IN_SPECIFIC_PROJECT, check_str='rule:' + base.ADMIN_IN_SYSTEM_PROJECTS,
description="Modify Service Parameter value.", description="Modify Service Parameter value.",
operations=[
{
'method': 'PATCH',
'path': '/v1/service_parameter/{parameter_id}'
}
]
) )
] ]

View File

@ -20,6 +20,7 @@ from pecan import hooks
from oslo_context import context as base_context from oslo_context import context as base_context
from oslo_utils import encodeutils from oslo_utils import encodeutils
from sysinv.api.policies import base as base_policy
from sysinv.common import policy from sysinv.common import policy
ALLOWED_WITHOUT_AUTH = '/' ALLOWED_WITHOUT_AUTH = '/'
@ -75,9 +76,9 @@ class RequestContext(base_context.RequestContext):
# Check user is admin or not # Check user is admin or not
if is_admin is None: if is_admin is None:
self.is_admin = policy.enforce(self, 'context_is_admin', self.is_admin = policy.authorize(
target={'project': self.project}, base_policy.ADMIN_IN_SYSTEM_PROJECTS, {}, self.to_dict(),
do_raise=False) do_raise=False)
else: else:
self.is_admin = is_admin self.is_admin = is_admin

View File

@ -1,134 +1,55 @@
# vim: tabstop=4 shiftwidth=4 softtabstop=4 # Copyright (c) 2022 Wind River Systems, Inc.
# Copyright (c) 2011 OpenStack Foundation
# All Rights Reserved.
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # 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 # not use this file except in compliance with the License. You may obtain
# a copy of the License at # a copy of the License at
# #
# http://www.apache.org/licenses/LICENSE-2.0 # http://www.apache.org/licenses/LICENSE-2.0
# #
# Unless required by applicable law or agreed to in writing, software # Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
#
# SPDX-License-Identifier: Apache-2.0
"""Policy Engine For Sysinv.""" """Policy Engine For Sysinv."""
import os.path
from oslo_config import cfg from oslo_config import cfg
from sysinv._i18n import _ from oslo_policy import policy
from sysinv.api import policies as controller_policies 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 = cfg.CONF
CONF.register_opts(policy_opts) _ENFORCER = None
_POLICY_PATH = None
_POLICY_CACHE = {}
def reset(): def reset():
global _POLICY_PATH """Discard current Enforcer object."""
global _POLICY_CACHE global _ENFORCER
_POLICY_PATH = None _ENFORCER = None
_POLICY_CACHE = {}
policy.reset()
def init(): def init(policy_file='policy.yaml'):
global _POLICY_PATH """Init an Enforcer class.
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)
:param policy_file: Custom policy file to be used.
def _set_rules(data): :return: Returns a Enforcer instance.
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.
""" """
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() init()
return _ENFORCER.authorize(rule, target, creds, do_raise=do_raise)
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

View File

@ -16,6 +16,10 @@
# under the License. # 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 Common Policy Engine Implementation
Policies can be expressed in one of two forms: A list of lists, or a Policies can be expressed in one of two forms: A list of lists, or a

View File

@ -47,8 +47,6 @@ class FunctionalTest(base.TestCase):
def setUp(self): def setUp(self):
super(FunctionalTest, self).setUp() super(FunctionalTest, self).setUp()
cfg.CONF.set_override("auth_version", "v2.0", group=acl.OPT_GROUP_NAME) 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.app = self._make_app()
self.dbapi = dbapi.get_instance() self.dbapi = dbapi.get_instance()
self.context = sysinv_context.RequestContext(is_admin=True) self.context = sysinv_context.RequestContext(is_admin=True)

View File

@ -16,10 +16,10 @@
policy_data = """ policy_data = """
{ ---
"admin_api": "role:admin", admin_api: "role:admin"
"admin_or_owner": "is_admin:True or project_id:%(project_id)s", admin_or_owner: "is_admin:True or project_id:%(project_id)s"
"is_admin": "role:admin or role:administrator", is_admin: "role:admin or role:administrator"
"default": "rule:admin_or_owner" default: "rule:admin_or_owner"
}
""" """

View File

@ -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",
}

View File

@ -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

View File

@ -18,7 +18,6 @@ import fixtures
from oslo_config import cfg from oslo_config import cfg
from sysinv.common import policy as sysinv_policy from sysinv.common import policy as sysinv_policy
from sysinv.openstack.common import policy as common_policy
from sysinv.tests import fake_policy from sysinv.tests import fake_policy
CONF = cfg.CONF CONF = cfg.CONF
@ -30,15 +29,9 @@ class PolicyFixture(fixtures.Fixture):
super(PolicyFixture, self).setUp() super(PolicyFixture, self).setUp()
self.policy_dir = self.useFixture(fixtures.TempDir()) self.policy_dir = self.useFixture(fixtures.TempDir())
self.policy_file_name = os.path.join(self.policy_dir.path, 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: with open(self.policy_file_name, 'w') as policy_file:
policy_file.write(fake_policy.policy_data) policy_file.write(fake_policy.policy_data)
CONF.set_override('policy_file', self.policy_file_name)
sysinv_policy.reset() sysinv_policy.reset()
sysinv_policy.init() sysinv_policy.init(self.policy_file_name)
self.addCleanup(sysinv_policy.reset) 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())))