Merge "Register and document policy in code"

This commit is contained in:
Zuul 2018-07-03 02:53:06 +00:00 committed by Gerrit Code Review
commit 6323ed802c
22 changed files with 259 additions and 119 deletions

3
.gitignore vendored
View File

@ -36,3 +36,6 @@ AUTHORS
ChangeLog
.testrepository
# generated policy file
etc/blazar/policy.yaml.sample

View File

@ -27,7 +27,7 @@ class API(object):
"""List all existing computehosts."""
return self.manager_rpcapi.list_computehosts()
@policy.authorize('oshosts', 'create')
@policy.authorize('oshosts', 'post')
@trusts.use_trust_auth()
def create_computehost(self, data):
"""Create new computehost.
@ -47,7 +47,7 @@ class API(object):
"""
return self.manager_rpcapi.get_computehost(host_id)
@policy.authorize('oshosts', 'update')
@policy.authorize('oshosts', 'put')
def update_computehost(self, host_id, data):
"""Update computehost. Only name changing may be proceeded.

View File

@ -40,7 +40,7 @@ class API(object):
project_id = ctx.project_id
return self.manager_rpcapi.list_leases(project_id=project_id)
@policy.authorize('leases', 'create')
@policy.authorize('leases', 'post')
@trusts.use_trust_auth()
def create_lease(self, data):
"""Create new lease.
@ -64,7 +64,7 @@ class API(object):
"""
return self.manager_rpcapi.get_lease(lease_id)
@policy.authorize('leases', 'update')
@policy.authorize('leases', 'put')
def update_lease(self, lease_id, data):
"""Update lease.

View File

@ -126,7 +126,7 @@ class HostsController(extensions.BaseController):
for host in
pecan.request.hosts_rpcapi.list_computehosts()]
@policy.authorize('oshosts', 'create')
@policy.authorize('oshosts', 'post')
@wsme_pecan.wsexpose(Host, body=Host, status_code=202)
@trusts.use_trust_auth()
def post(self, host):
@ -144,7 +144,7 @@ class HostsController(extensions.BaseController):
else:
raise exceptions.BlazarException(_("Host can't be created"))
@policy.authorize('oshosts', 'update')
@policy.authorize('oshosts', 'put')
@wsme_pecan.wsexpose(Host, types.IntegerType(), body=Host,
status_code=202)
def put(self, id, host):

View File

@ -103,7 +103,7 @@ class LeasesController(extensions.BaseController):
return [Lease.convert(lease)
for lease in pecan.request.rpcapi.list_leases()]
@policy.authorize('leases', 'create')
@policy.authorize('leases', 'post')
@wsme_pecan.wsexpose(Lease, body=Lease, status_code=202)
@trusts.use_trust_auth()
def post(self, lease):
@ -120,7 +120,7 @@ class LeasesController(extensions.BaseController):
else:
raise exceptions.BlazarException(_("Lease can't be created"))
@policy.authorize('leases', 'update')
@policy.authorize('leases', 'put')
@wsme_pecan.wsexpose(Lease, types.UuidType(), body=Lease, status_code=202)
def put(self, id, sublease):
"""Update an existing lease.

View File

@ -0,0 +1,25 @@
# 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 blazar.policies import base
from blazar.policies import leases
from blazar.policies import oshosts
def list_rules():
return itertools.chain(
base.list_rules(),
leases.list_rules(),
oshosts.list_rules(),
)

32
blazar/policies/base.py Normal file
View File

@ -0,0 +1,32 @@
# 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
RULE_ADMIN = 'rule:admin'
RULE_ADMIN_OR_OWNER = 'rule:admin_or_owner'
RULE_ANY = '@'
rules = [
policy.RuleDefault(
name="admin",
check_str="is_admin:True or role:admin",
description="Default rule for most Admin APIs."),
policy.RuleDefault(
name="admin_or_owner",
check_str="rule:admin or project_id:%(project_id)s",
description="Default rule for most non-Admin APIs.")
]
def list_rules():
return rules

72
blazar/policies/leases.py Normal file
View File

@ -0,0 +1,72 @@
# 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 blazar.policies import base
POLICY_ROOT = 'blazar:leases:%s'
leases_policies = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for List/Show Lease(s) API.',
operations=[
{
'path': '/{api_version}/leases',
'method': 'GET'
},
{
'path': '/{api_version}/leases/{lease_id}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'post',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for Create Lease API.',
operations=[
{
'path': '/{api_version}/leases',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'put',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for Update Lease API.',
operations=[
{
'path': '/{api_version}/leases/{lease_id}',
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete',
check_str=base.RULE_ADMIN_OR_OWNER,
description='Policy rule for Delete Lease API.',
operations=[
{
'path': '/{api_version}/leases/{lease_id}',
'method': 'DELETE'
}
]
)
]
def list_rules():
return leases_policies

View File

@ -0,0 +1,72 @@
# 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 blazar.policies import base
POLICY_ROOT = 'blazar:oshosts:%s'
oshosts_policies = [
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'get',
check_str=base.RULE_ADMIN,
description='Policy rule for List/Show Host(s) API.',
operations=[
{
'path': '/{api_version}/os-hosts',
'method': 'GET'
},
{
'path': '/{api_version}/os-hosts/{host_id}',
'method': 'GET'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'post',
check_str=base.RULE_ADMIN,
description='Policy rule for Create Host API.',
operations=[
{
'path': '/{api_version}/os-hosts',
'method': 'POST'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'put',
check_str=base.RULE_ADMIN,
description='Policy rule for Update Host API.',
operations=[
{
'path': '/{api_version}/os-hosts/{host_id}',
'method': 'PUT'
}
]
),
policy.DocumentedRuleDefault(
name=POLICY_ROOT % 'delete',
check_str=base.RULE_ADMIN,
description='Policy rule for Delete Host API.',
operations=[
{
'path': '/{api_version}/os-hosts/{host_id}',
'method': 'DELETE'
}
]
)
]
def list_rules():
return oshosts_policies

View File

@ -24,6 +24,7 @@ from oslo_policy import policy
from blazar import context
from blazar import exceptions
from blazar import policies
CONF = cfg.CONF
opts.set_defaults(CONF)
@ -45,6 +46,7 @@ def init():
if not _ENFORCER:
LOG.debug("Enforcer not present, recreating at init stage.")
_ENFORCER = policy.Enforcer(CONF)
_ENFORCER.register_defaults(policies.list_rules())
def set_rules(data, default_rule=None):

View File

@ -13,8 +13,6 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import os
import fixtures
import tempfile
import testscenarios
@ -26,8 +24,6 @@ from oslotest import base
from blazar import context
from blazar.db.sqlalchemy import api as db_api
from blazar.db.sqlalchemy import facade_wrapper
from blazar import policy
from blazar.tests import fake_policy
cfg.CONF.set_override('use_stderr', False)
@ -52,22 +48,6 @@ class Database(fixtures.Fixture):
self.addCleanup(db_api.drop_db)
class PolicyFixture(fixtures.Fixture):
def setUp(self):
super(PolicyFixture, self).setUp()
self.policy_dir = self.useFixture(fixtures.TempDir())
self.policy_file_name = os.path.join(self.policy_dir.path,
'policy.json')
with open(self.policy_file_name, 'w') as policy_file:
policy_file.write(fake_policy.policy_data)
cfg.CONF.set_override('policy_file', self.policy_file_name,
group='oslo_policy')
policy.reset()
policy.init()
self.addCleanup(policy.reset)
class TestCase(testscenarios.WithScenarios, base.BaseTestCase):
"""Test case base class for all unit tests.
@ -80,7 +60,6 @@ class TestCase(testscenarios.WithScenarios, base.BaseTestCase):
super(TestCase, self).setUp()
self.context_mock = None
cfg.CONF(args=[], project='blazar')
self.policy = self.useFixture(PolicyFixture())
def patch(self, obj, attr):
"""Returns a Mocked object on the patched attribute."""

View File

@ -1,39 +0,0 @@
# Copyright (c) 2013 Bull.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
policy_data = """
{
"admin": "is_admin:True or role:admin or role:masterofuniverse",
"admin_or_owner": "rule:admin or project_id:%(project_id)s",
"default": "!",
"admin_api": "rule:admin",
"blazar:leases": "rule:admin_or_owner",
"blazar:oshosts": "rule:admin_api",
"blazar:leases:get": "rule:admin_or_owner",
"blazar:leases:create": "rule:admin_or_owner",
"blazar:leases:delete": "rule:admin_or_owner",
"blazar:leases:update": "rule:admin_or_owner",
"blazar:plugins:get": "@",
"blazar:oshosts:get": "rule:admin_api",
"blazar:oshosts:create": "rule:admin_api",
"blazar:oshosts:delete": "rule:admin_api",
"blazar:oshosts:update": "rule:admin_api"
}
"""

View File

@ -39,7 +39,7 @@ class BlazarPolicyTestCase(tests.TestCase):
'project_id': self.context.project_id}
target_wrong = {'user_id': self.context.user_id,
'project_id': 'bad_project'}
action = "blazar:leases"
action = "blazar:leases:get"
self.assertTrue(policy.enforce(self.context, action,
target_good))
self.assertFalse(policy.enforce(self.context, action,
@ -48,14 +48,14 @@ class BlazarPolicyTestCase(tests.TestCase):
def test_adminpolicy(self):
target = {'user_id': self.context.user_id,
'project_id': self.context.project_id}
action = "blazar:oshosts"
action = "blazar:oshosts:get"
self.assertRaises(exceptions.PolicyNotAuthorized, policy.enforce,
self.context, action, target)
def test_elevatedpolicy(self):
target = {'user_id': self.context.user_id,
'project_id': self.context.project_id}
action = "blazar:oshosts"
action = "blazar:oshosts:get"
self.assertRaises(exceptions.PolicyNotAuthorized, policy.enforce,
self.context, action, target)
elevated_context = self.context.elevated()
@ -63,23 +63,14 @@ class BlazarPolicyTestCase(tests.TestCase):
def test_authorize(self):
@policy.authorize('leases', ctx=self.context)
def user_method(self):
return True
@policy.authorize('leases', 'get', ctx=self.context)
def user_method_with_action(self):
return True
@policy.authorize('oshosts', ctx=self.context)
def adminonly_method(self):
@policy.authorize('oshosts', 'get', ctx=self.context)
def adminonly_method_with_action(self):
return True
self.assertTrue(user_method(self))
self.assertTrue(user_method_with_action(self))
try:
adminonly_method(self)
self.assertTrue(False)
except exceptions.PolicyNotAuthorized:
# We are expecting this exception
self.assertTrue(True)
self.assertRaises(exceptions.PolicyNotAuthorized,
adminonly_method_with_action, self)

View File

@ -41,9 +41,6 @@ function configure_blazar {
fi
sudo chown $STACK_USER $BLAZAR_CONF_DIR
BLAZAR_POLICY_FILE=$BLAZAR_CONF_DIR/policy.json
cp $BLAZAR_DIR/etc/policy.json $BLAZAR_POLICY_FILE
touch $BLAZAR_CONF_FILE
iniset $BLAZAR_CONF_FILE DEFAULT os_auth_version v3

View File

@ -39,6 +39,8 @@ extensions = ['sphinx.ext.autodoc',
'openstackdocstheme',
'oslo_config.sphinxext',
'oslo_config.sphinxconfiggen',
'oslo_policy.sphinxpolicygen',
'oslo_policy.sphinxext',
]
# openstackdocstheme options
@ -46,11 +48,16 @@ repository_name = 'openstack/blazar'
bug_project = 'blazar'
bug_tag = ''
wsme_protocols = ['restjson', 'restxml']
# oslo_config.sphinxconfiggen options
config_generator_config_file = '../../etc/blazar/blazar-config-generator.conf'
sample_config_basename = '_static/blazar'
wsme_protocols = ['restjson', 'restxml']
# oslo_policy.sphinxpolicygen options
policy_generator_config_file = [
('../../etc/blazar/blazar-policy-generator.conf', '_static/blazar'),
]
# The suffix of source filenames.
source_suffix = '.rst'

View File

@ -2,4 +2,11 @@
Policies
========
Please see a sample policy file: :doc:`/configuration/samples/blazar-policy`.
The following is an overview of all available policies in Blazar. For a sample
configuration file, refer to :doc:`/configuration/samples/blazar-policy`.
To change policies, please create a policy file in */etc/blazar/* and specify
the policy file name at the *oslo_policy/policy_file* option in *blazar.conf*.
.. show-policy::
:config-file: etc/blazar/blazar-policy-generator.conf

View File

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

View File

@ -32,14 +32,6 @@ or
..
Next you need to create a Blazar policy file:
.. sourcecode:: console
cp /path/to/blazar/etc/policy.json /etc/blazar/
..
Next you need to configure Blazar and Nova. First, generate a blazar.conf sample:
.. sourcecode:: console

View File

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

View File

@ -1,20 +0,0 @@
{
"admin": "is_admin:True or role:admin or role:masterofuniverse",
"admin_or_owner": "rule:admin or project_id:%(project_id)s",
"default": "!",
"admin_api": "rule:admin",
"blazar:leases:get": "rule:admin_or_owner",
"blazar:leases:create": "rule:admin_or_owner",
"blazar:leases:delete": "rule:admin_or_owner",
"blazar:leases:update": "rule:admin_or_owner",
"blazar:plugins:get": "@",
"blazar:oshosts:get": "rule:admin_or_owner",
"blazar:oshosts:create": "rule:admin_api",
"blazar:oshosts:delete": "rule:admin_api",
"blazar:oshosts:update": "rule:admin_api"
}

View File

@ -46,6 +46,9 @@ blazar.api.v2.controllers.extensions =
oslo.config.opts =
blazar = blazar.opts:list_opts
oslo.policy.policies =
blazar = blazar.policies:list_rules
wsgi_scripts =
blazar-api-wsgi = blazar.api.wsgi_app:init_app

View File

@ -68,6 +68,11 @@ basepython = python3
commands =
rm -rf api-ref/build
sphinx-build -WE -b html -d api-ref/build/doctrees api-ref/source api-ref/build/html
[testenv:genpolicy]
commands =
oslopolicy-sample-generator --config-file etc/blazar/blazar-policy-generator.conf
[testenv:lower-constraints]
basepython = python3
deps =