Implement policy in code

* Added policy in code as per community goals [1] for vnf packages.
* Modified tox to generate policy sample json file.

[1]: https://governance.openstack.org/tc/goals/queens/policy-in-code.html

Partial-Implements: blueprint tosca-csar-mgmt-driver

Co-Author: Neha Alhat <neha.alhat@nttdata.com>
Co-Author: Bhagyashri Shewale <bhagyashri.shewale@nttdata.com>
Change-Id: I7cedbca4abe41223e3f8d6211a74b4347299e9e5
This commit is contained in:
Niraj Singh 2019-08-08 08:59:06 +00:00 committed by nirajsingh
parent 354da78e0c
commit 34b661ad08
19 changed files with 303 additions and 35 deletions

2
.gitignore vendored
View File

@ -28,7 +28,9 @@ subunit.log
.eggs/
.stestr/
SP1_res.yaml
etc/tacker/policy.yaml.sample
releasenotes/build
etc/tacker/tacker.conf.sample
doc/source/contributor/api
doc/source/_static/tacker.policy.yaml.sample

View File

@ -200,16 +200,10 @@ function configure_tacker {
# server
TACKER_API_PASTE_FILE=$TACKER_CONF_DIR/api-paste.ini
TACKER_POLICY_FILE=$TACKER_CONF_DIR/policy.json
cp $TACKER_DIR/etc/tacker/api-paste.ini $TACKER_API_PASTE_FILE
cp $TACKER_DIR/etc/tacker/policy.json $TACKER_POLICY_FILE
# allow tacker user to administer tacker to match tacker account
sed -i 's/"context_is_admin": "role:admin"/"context_is_admin": "role:admin or user_name:tacker"/g' $TACKER_POLICY_FILE
iniset $TACKER_CONF DEFAULT debug $ENABLE_DEBUG_LOG_LEVEL
iniset $TACKER_CONF DEFAULT policy_file $TACKER_POLICY_FILE
iniset $TACKER_CONF DEFAULT auth_strategy $TACKER_AUTH_STRATEGY
_tacker_setup_keystone $TACKER_CONF keystone_authtoken

View File

@ -1,6 +1,5 @@
[DEFAULT]
auth_strategy = keystone
policy_file = /etc/tacker/policy.json
debug = True
logging_exception_prefix = %(color)s%(asctime)s.%(msecs)03d TRACE %(name)s %(instance)s
logging_debug_format_suffix = from (pid=%(process)d) %(funcName)s %(pathname)s:%(lineno)d

View File

@ -23,8 +23,15 @@ extensions = [
'sphinxcontrib.apidoc',
'stevedore.sphinxext',
'openstackdocstheme',
'oslo_policy.sphinxext',
'oslo_policy.sphinxpolicygen',
]
policy_generator_config_file = [
('../../etc/tacker-policy-generator.conf', '_static/tacker'),
]
sample_policy_basename = '_static/tacker'
# sphinxcontrib.apidoc options
apidoc_module_dir = '../../tacker'
apidoc_output_dir = 'contributor/api'

View File

@ -31,3 +31,24 @@ The sample configuration can also be viewed in :download:`file form
version of this documentation.
.. literalinclude:: /_extra/tacker.conf.sample
Policy
------
Tacker, like most OpenStack projects, uses a policy language to restrict
permissions on REST API actions.
* :doc:`Policy Reference <policy>`: A complete reference of all
policy points in tacker and what they impact.
* :doc:`Sample Policy File <sample_policy>`: A sample tacker
policy file with inline documentation.
.. # NOTE(bhagyashris): This is the section where we hide things that we don't
# actually want in the table of contents but sphinx build would fail if
# they aren't in the toctree somewhere.
.. toctree::
:hidden:
policy
sample_policy

View File

@ -0,0 +1,9 @@
===============
Tacker Policies
===============
The following is an overview of all available policies in Tacker.
For a sample configuration file, refer to :doc:`/configuration/sample_policy`.
.. show-policy::
:config-file: etc/tacker-policy-generator.conf

View File

@ -0,0 +1,16 @@
=========================
Sample Tacker Policy File
=========================
The following is a sample tacker policy file for adaptation and use.
The sample policy can also be viewed in :download:`file form
</_static/tacker.policy.yaml.sample>`.
.. important::
The sample policy file is auto-generated from tacker when this documentation
is built. You must ensure your version of tacker matches the version of this
documentation.
.. literalinclude:: /_static/tacker.policy.yaml.sample

View File

@ -0,0 +1,3 @@
[DEFAULT]
output_file = etc/tacker/policy.yaml.sample
namespace = tacker

View File

@ -0,0 +1,7 @@
Tacker
======
To generate the sample tacker policy.yaml file, run the following command from
the top level of the tacker directory:
tox -egenpolicy

View File

@ -1,10 +0,0 @@
{
"context_is_admin": "role:admin",
"admin_or_owner": "rule:context_is_admin or tenant_id:%(tenant_id)s",
"admin_only": "rule:context_is_admin",
"regular_user": "",
"shared": "field:vims:shared=True",
"default": "rule:admin_or_owner",
"get_vim": "rule:admin_or_owner or rule:shared"
}

View File

@ -25,7 +25,6 @@ packages =
data_files =
etc/tacker =
etc/tacker/api-paste.ini
etc/tacker/policy.json
etc/tacker/rootwrap.conf
etc/rootwrap.d =
etc/tacker/rootwrap.d/tacker.filters
@ -96,6 +95,15 @@ oslo.config.opts =
mistral.actions =
tacker.vim_ping_action = tacker.nfvo.workflows.vim_monitor.vim_ping_action:PingVimAction
oslo.policy.enforcer =
tacker = tacker.policy:get_enforcer
oslo.policy.policies =
# The sample policies will be ordered by entry point and then by list
# returned from that entry point. If more control is desired split out each
# list_rules method into a separate entry point rather than using the
# aggregate method.
tacker = tacker.policies:list_rules
[build_releasenotes]
all_files = 1

View File

@ -43,8 +43,6 @@ core_opts = [
help=_("The path for API extensions")),
cfg.ListOpt('service_plugins', default=['nfvo', 'vnfm'],
help=_("The service plugins Tacker will use")),
cfg.StrOpt('policy_file', default="policy.json",
help=_("The policy file to use")),
cfg.StrOpt('auth_strategy', default='keystone',
help=_("The type of authentication to use")),
cfg.BoolOpt('allow_bulk', default=True,

View File

@ -23,6 +23,7 @@ from oslo_config import cfg
from oslo_context import context as oslo_context
from oslo_db.sqlalchemy import enginefacade
from tacker.common import exceptions
from tacker.db import api as db_api
from tacker import policy
@ -142,6 +143,34 @@ class ContextBase(oslo_context.RequestContext):
return context
def can(self, action, target=None, fatal=True):
"""Verifies that the given action is valid on the target in this context.
:param action: string representing the action to be checked.
: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}``.
If None, then this default target will be considered:
{'project_id': self.project_id, 'user_id': self.user_id}
:param fatal: if False, will return False when an exception.Forbidden
occurs.
:raises tacker.exception.Forbidden: if verification fails and fatal
is True.
:return: returns a non-False value (not necessarily "True") if
authorized and False if not authorized and fatal is False.
"""
if target is None:
target = {'tenant_id': self.tenant_id,
'user_id': self.user_id}
try:
return policy.authorize(self, action, target)
except exceptions.Forbidden:
if fatal:
raise
return False
@enginefacade.transaction_context_provider
class ContextBaseWithSession(ContextBase):

View File

@ -0,0 +1,27 @@
# Copyright (C) 2019 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import itertools
from tacker.policies import base
from tacker.policies import vnf_package
def list_rules():
return itertools.chain(
base.list_rules(),
vnf_package.list_rules(),
)

49
tacker/policies/base.py Normal file
View File

@ -0,0 +1,49 @@
# Copyright (C) 2019 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
TACKER_API = 'os_nfv_orchestration_api'
RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
RULE_ADMIN_API = 'rule:admin_only'
RULE_ANY = '@'
rules = [
policy.RuleDefault(
"context_is_admin",
"role:admin",
"Decides what is required for the 'is_admin:True' check to succeed."),
policy.RuleDefault(
"admin_or_owner",
"is_admin:True or tenant_id:%(tenant_id)s",
"Default rule for most non-Admin APIs."),
policy.RuleDefault(
"admin_only",
"is_admin:True",
"Default rule for most Admin APIs."),
policy.RuleDefault(
"shared",
"field:vims:shared=True",
"Default rule for sharing vims."),
policy.RuleDefault(
"default",
"rule:admin_or_owner",
"Default rule for most non-Admin APIs.")
]
def list_rules():
return rules

View File

@ -0,0 +1,91 @@
# Copyright (C) 2019 NTT DATA
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from oslo_policy import policy
from tacker.policies import base
VNFPKGM = 'os_nfv_orchestration_api:vnf_packages:%s'
rules = [
policy.DocumentedRuleDefault(
name=VNFPKGM % 'create',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Creates a vnf package.",
operations=[
{
'method': 'POST',
'path': '/vnf_packages'
}
]),
policy.DocumentedRuleDefault(
name=VNFPKGM % 'show',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Show a vnf package.",
operations=[
{
'method': 'GET',
'path': '/vnf_packages/{vnf_package_id}'
}
]),
policy.DocumentedRuleDefault(
name=VNFPKGM % 'index',
check_str=base.RULE_ADMIN_OR_OWNER,
description="List all vnf packages.",
operations=[
{
'method': 'GET',
'path': '/vnf_packages/'
}
]),
policy.DocumentedRuleDefault(
name=VNFPKGM % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
description="Delete a vnf package.",
operations=[
{
'method': 'DELETE',
'path': '/vnf_packages/{vnf_package_id}'
}
]),
policy.DocumentedRuleDefault(
name=VNFPKGM % 'upload_package_content',
check_str=base.RULE_ADMIN_OR_OWNER,
description="upload a vnf package content.",
operations=[
{
'method': 'PUT',
'path': '/vnf_packages/{vnf_package_id}/'
'package_content'
}
]),
policy.DocumentedRuleDefault(
name=VNFPKGM % 'upload_from_uri',
check_str=base.RULE_ADMIN_OR_OWNER,
description="upload a vnf package content from uri.",
operations=[
{
'method': 'POST',
'path': '/vnf_packages/{vnf_package_id}/package_content/'
'upload_from_uri'
}
]),
]
def list_rules():
return rules

View File

@ -28,6 +28,7 @@ import six
from tacker._i18n import _
from tacker.api.v1 import attributes
from tacker.common import exceptions
from tacker import policies
LOG = logging.getLogger(__name__)
@ -36,18 +37,6 @@ _ENFORCER = None
ADMIN_CTX_POLICY = 'context_is_admin'
_BASE_RULES = [
policy.RuleDefault(
ADMIN_CTX_POLICY,
'role:admin',
description='Rule for cloud admin access'),
# policy.RuleDefault(
# _ADVSVC_CTX_POLICY,
# 'role:advsvc',
# description='Rule for advanced service role access'),
]
def reset():
global _ENFORCER
if _ENFORCER:
@ -61,8 +50,29 @@ def init(conf=cfg.CONF, policy_file=None):
global _ENFORCER
if not _ENFORCER:
_ENFORCER = policy.Enforcer(conf, policy_file=policy_file)
_ENFORCER.register_defaults(_BASE_RULES)
_ENFORCER.load_rules(True)
register_rules(_ENFORCER)
_ENFORCER.load_rules()
def authorize(context, action, target, do_raise=True, exc=None):
init()
credentials = context.to_policy_values()
if not exc:
exc = exceptions.PolicyNotAuthorized
try:
result = _ENFORCER.authorize(action, target, credentials,
do_raise=do_raise, exc=exc, action=action)
except policy.PolicyNotRegistered:
with excutils.save_and_reraise_exception():
LOG.debug('Policy not registered')
except Exception:
with excutils.save_and_reraise_exception():
LOG.debug('Policy check for %(action)s failed with credentials '
'%(credentials)s',
{'action': action, 'credentials': credentials})
return result
def refresh(policy_file=None):
@ -427,6 +437,10 @@ def check_is_admin(context):
return False
def register_rules(enforcer):
enforcer.register_defaults(policies.list_rules())
def get_enforcer():
# NOTE(amotoki): This was borrowed from nova/policy.py.
# This method is for use by oslo.policy CLI scripts. Those scripts need the

View File

@ -29,7 +29,6 @@ class ConfigurationTest(base.BaseTestCase):
self.assertEqual(9890, cfg.CONF.bind_port)
self.assertEqual('api-paste.ini.test', cfg.CONF.api_paste_config)
self.assertEqual('unit/extensions', cfg.CONF.api_extensions_path)
self.assertEqual('policy.json', cfg.CONF.policy_file)
self.assertEqual('keystone', cfg.CONF.auth_strategy)
self.assertTrue(cfg.CONF.allow_bulk)
self.assertFalse(cfg.CONF.allow_pagination)

View File

@ -60,6 +60,7 @@ commands = python ./tools/check_i18n.py ./tacker
deps = -r{toxinidir}/doc/requirements.txt
commands =
sphinx-build -W -b html doc/source doc/build/html
oslopolicy-sample-generator --config-file=etc/tacker-policy-generator.conf
[testenv:api-ref]
deps = -r{toxinidir}/doc/requirements.txt
@ -101,6 +102,10 @@ local-check-factory = tacker.hacking.checks.factory
commands =
oslo-config-generator --config-file=etc/config-generator.conf
[testenv:genpolicy]
commands =
oslopolicy-sample-generator --config-file=etc/tacker-policy-generator.conf
[testenv:lower-constraints]
deps =
-c{toxinidir}/lower-constraints.txt