Move policy defaults into code
Instead of a default policy.json file, policy defaults are now defined in code. An operator need not supply policy.json data except to the extent they want to override the defaults. Currently an empty policy.json is still shipped because it is expected by devstack, but this can be removed later. A sample policy.yaml file can be generated using the genpolicy tox environment. This partly fulfils the requirements of the policy in code goal[1]. However, because policies don't map 1:1 with APIs, it will not be possible to fully document the policies until changes are made in how policies are applied as proposed in https://review.opendev.org/528021 Due to the fact that existing policy files may rely on a rule named "default" to specifiy policies not explicitly listed in the policy.json file, all policies that are not admin-only by default now default to "rule:default", so that the "default" rule will continue to apply to those policies that are not listed in policy.json. To ensure that this yields the expected policy in a standard policy-in-code config file, the default value of the "default" rule is now the empty string "". This is a change; between the Queens release and now the default was set to "role:admin" to match the value specified in the default policy.json file. An installation relying on both the "default" rule for some policies and the default value of the default rule may end up with a more permissive policy after upgrading. It's likely that no such policies exist in the wild, because prior to the Queens release the default value for the "default" rule was "@" (allow all requests), so anybody relying on this rule will surely have specified it explicitly in their policy.json. Policies whose default is "role:admin" no longer use the "default" rule. Therefore existing policy.json files that rely on the "default" rule for those policies, and who have specified a value for the "default" rule that is more permissive, will result in a more restrictive policy after upgrading. It is unlikely that any of these policies exist in the wild either. [1] https://governance.openstack.org/tc/goals/selected/queens/policy-in-code.html Change-Id: I8d1ccf5844078cc0b1652fb1130794daf07cedbc
This commit is contained in:
parent
0c6dd98d01
commit
5c17e4c7ef
3
.gitignore
vendored
3
.gitignore
vendored
@ -22,6 +22,9 @@ tests.sqlite
|
||||
glance/versioninfo
|
||||
subunit.log
|
||||
|
||||
# generated policy file
|
||||
etc/policy.yaml.sample
|
||||
|
||||
# Swap files range from .saa to .swp
|
||||
*.s[a-w][a-p]
|
||||
|
||||
|
3
etc/glance-policy-generator.conf
Normal file
3
etc/glance-policy-generator.conf
Normal file
@ -0,0 +1,3 @@
|
||||
[DEFAULT]
|
||||
namespace = glance
|
||||
output_file = etc/policy.yaml.sample
|
@ -1,63 +1,2 @@
|
||||
{
|
||||
"context_is_admin": "role:admin",
|
||||
"default": "role:admin",
|
||||
|
||||
"add_image": "",
|
||||
"delete_image": "",
|
||||
"get_image": "",
|
||||
"get_images": "",
|
||||
"modify_image": "",
|
||||
"publicize_image": "role:admin",
|
||||
"communitize_image": "",
|
||||
"copy_from": "",
|
||||
|
||||
"download_image": "",
|
||||
"upload_image": "",
|
||||
|
||||
"delete_image_location": "",
|
||||
"get_image_location": "",
|
||||
"set_image_location": "",
|
||||
|
||||
"add_member": "",
|
||||
"delete_member": "",
|
||||
"get_member": "",
|
||||
"get_members": "",
|
||||
"modify_member": "",
|
||||
|
||||
"manage_image_cache": "role:admin",
|
||||
|
||||
"get_task": "",
|
||||
"get_tasks": "",
|
||||
"add_task": "",
|
||||
"modify_task": "",
|
||||
"tasks_api_access": "role:admin",
|
||||
|
||||
"deactivate": "",
|
||||
"reactivate": "",
|
||||
|
||||
"get_metadef_namespace": "",
|
||||
"get_metadef_namespaces":"",
|
||||
"modify_metadef_namespace":"",
|
||||
"add_metadef_namespace":"",
|
||||
|
||||
"get_metadef_object":"",
|
||||
"get_metadef_objects":"",
|
||||
"modify_metadef_object":"",
|
||||
"add_metadef_object":"",
|
||||
|
||||
"list_metadef_resource_types":"",
|
||||
"get_metadef_resource_type":"",
|
||||
"add_metadef_resource_type_association":"",
|
||||
|
||||
"get_metadef_property":"",
|
||||
"get_metadef_properties":"",
|
||||
"modify_metadef_property":"",
|
||||
"add_metadef_property":"",
|
||||
|
||||
"get_metadef_tag":"",
|
||||
"get_metadef_tags":"",
|
||||
"modify_metadef_tag":"",
|
||||
"add_metadef_tag":"",
|
||||
"add_metadef_tags":""
|
||||
|
||||
}
|
||||
|
@ -32,34 +32,26 @@ from oslo_policy import policy
|
||||
from glance.common import exception
|
||||
import glance.domain.proxy
|
||||
from glance.i18n import _
|
||||
from glance import policies
|
||||
|
||||
|
||||
LOG = logging.getLogger(__name__)
|
||||
CONF = cfg.CONF
|
||||
_ENFORCER = None
|
||||
|
||||
DEFAULT_RULES = policy.Rules.from_dict({
|
||||
'context_is_admin': 'role:admin',
|
||||
'default': 'role:admin',
|
||||
'manage_image_cache': 'role:admin',
|
||||
})
|
||||
|
||||
|
||||
class Enforcer(policy.Enforcer):
|
||||
"""Responsible for loading and enforcing rules"""
|
||||
|
||||
def __init__(self):
|
||||
if CONF.find_file(CONF.oslo_policy.policy_file):
|
||||
kwargs = dict(rules=None, use_conf=True)
|
||||
else:
|
||||
kwargs = dict(rules=DEFAULT_RULES, use_conf=False)
|
||||
super(Enforcer, self).__init__(CONF, overwrite=False, **kwargs)
|
||||
super(Enforcer, self).__init__(CONF, use_conf=True, overwrite=False)
|
||||
self.register_defaults(policies.list_rules())
|
||||
|
||||
def add_rules(self, rules):
|
||||
"""Add new rules to the Rules object"""
|
||||
self.set_rules(rules, overwrite=False, use_conf=self.use_conf)
|
||||
|
||||
def enforce(self, context, action, target):
|
||||
def enforce(self, context, action, target, registered=True):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: Glance request context
|
||||
@ -68,13 +60,15 @@ class Enforcer(policy.Enforcer):
|
||||
:raises: `glance.common.exception.Forbidden`
|
||||
:returns: A non-False value if access is allowed.
|
||||
"""
|
||||
if registered and action not in self.registered_rules:
|
||||
raise policy.PolicyNotRegistered(action)
|
||||
return super(Enforcer, self).enforce(action, target,
|
||||
context.to_policy_values(),
|
||||
do_raise=True,
|
||||
exc=exception.Forbidden,
|
||||
action=action)
|
||||
|
||||
def check(self, context, action, target):
|
||||
def check(self, context, action, target, registered=True):
|
||||
"""Verifies that the action is valid on the target in this context.
|
||||
|
||||
:param context: Glance request context
|
||||
@ -82,6 +76,8 @@ class Enforcer(policy.Enforcer):
|
||||
:param target: Dictionary representing the object of the action.
|
||||
:returns: A non-False value if access is allowed.
|
||||
"""
|
||||
if registered and action not in self.registered_rules:
|
||||
raise policy.PolicyNotRegistered(action)
|
||||
return super(Enforcer, self).enforce(action,
|
||||
target,
|
||||
context.to_policy_values())
|
||||
|
@ -209,7 +209,7 @@ class PropertyRules(object):
|
||||
def _check_policy(self, property_exp, action, context):
|
||||
try:
|
||||
action = ":".join([property_exp, action])
|
||||
self.policy_enforcer.enforce(context, action, {})
|
||||
self.policy_enforcer.enforce(context, action, {}, registered=False)
|
||||
except exception.Forbidden:
|
||||
return False
|
||||
return True
|
||||
|
27
glance/policies/__init__.py
Normal file
27
glance/policies/__init__.py
Normal file
@ -0,0 +1,27 @@
|
||||
# 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 glance.policies import base
|
||||
from glance.policies import image
|
||||
from glance.policies import metadef
|
||||
from glance.policies import tasks
|
||||
|
||||
|
||||
def list_rules():
|
||||
return itertools.chain(
|
||||
base.list_rules(),
|
||||
image.list_rules(),
|
||||
tasks.list_rules(),
|
||||
metadef.list_rules(),
|
||||
)
|
28
glance/policies/base.py
Normal file
28
glance/policies/base.py
Normal file
@ -0,0 +1,28 @@
|
||||
# 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
|
||||
|
||||
|
||||
rules = [
|
||||
policy.RuleDefault(name='default', check_str='',
|
||||
description='Defines the default rule used for '
|
||||
'policies that historically had an empty '
|
||||
'policy in the supplied policy.json file.'),
|
||||
policy.RuleDefault(name='context_is_admin', check_str='role:admin',
|
||||
description='Defines the rule for the is_admin:True '
|
||||
'check.'),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return rules
|
47
glance/policies/image.py
Normal file
47
glance/policies/image.py
Normal file
@ -0,0 +1,47 @@
|
||||
# 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
|
||||
|
||||
|
||||
image_policies = [
|
||||
policy.RuleDefault(name="add_image", check_str="rule:default"),
|
||||
policy.RuleDefault(name="delete_image", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_image", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_images", check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_image", check_str="rule:default"),
|
||||
policy.RuleDefault(name="publicize_image", check_str="role:admin"),
|
||||
policy.RuleDefault(name="communitize_image", check_str="rule:default"),
|
||||
policy.RuleDefault(name="copy_from", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="download_image", check_str="rule:default"),
|
||||
policy.RuleDefault(name="upload_image", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="delete_image_location", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_image_location", check_str="rule:default"),
|
||||
policy.RuleDefault(name="set_image_location", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="add_member", check_str="rule:default"),
|
||||
policy.RuleDefault(name="delete_member", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_member", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_members", check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_member", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="manage_image_cache", check_str="role:admin"),
|
||||
|
||||
policy.RuleDefault(name="deactivate", check_str="rule:default"),
|
||||
policy.RuleDefault(name="reactivate", check_str="rule:default"),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return image_policies
|
52
glance/policies/metadef.py
Normal file
52
glance/policies/metadef.py
Normal file
@ -0,0 +1,52 @@
|
||||
# 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
|
||||
|
||||
|
||||
metadef_policies = [
|
||||
policy.RuleDefault(name="get_metadef_namespace", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_metadef_namespaces",
|
||||
check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_metadef_namespace",
|
||||
check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_metadef_namespace", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="get_metadef_object", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_metadef_objects", check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_metadef_object", check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_metadef_object", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="list_metadef_resource_types",
|
||||
check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_metadef_resource_type",
|
||||
check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_metadef_resource_type_association",
|
||||
check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="get_metadef_property", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_metadef_properties",
|
||||
check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_metadef_property",
|
||||
check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_metadef_property", check_str="rule:default"),
|
||||
|
||||
policy.RuleDefault(name="get_metadef_tag", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_metadef_tags", check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_metadef_tag", check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_metadef_tag", check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_metadef_tags", check_str="rule:default"),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return metadef_policies
|
26
glance/policies/tasks.py
Normal file
26
glance/policies/tasks.py
Normal file
@ -0,0 +1,26 @@
|
||||
# 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
|
||||
|
||||
|
||||
task_policies = [
|
||||
policy.RuleDefault(name="get_task", check_str="rule:default"),
|
||||
policy.RuleDefault(name="get_tasks", check_str="rule:default"),
|
||||
policy.RuleDefault(name="add_task", check_str="rule:default"),
|
||||
policy.RuleDefault(name="modify_task", check_str="rule:default"),
|
||||
policy.RuleDefault(name="tasks_api_access", check_str="role:admin"),
|
||||
]
|
||||
|
||||
|
||||
def list_rules():
|
||||
return task_policies
|
@ -162,6 +162,22 @@ class TaskFactoryStub(object):
|
||||
|
||||
class TestPolicyEnforcer(base.IsolatedUnitTest):
|
||||
|
||||
def test_policy_enforce_unregistered(self):
|
||||
enforcer = glance.api.policy.Enforcer()
|
||||
context = glance.context.RequestContext(roles=[])
|
||||
|
||||
self.assertRaises(glance.api.policy.policy.PolicyNotRegistered,
|
||||
enforcer.enforce,
|
||||
context, 'wibble', {})
|
||||
|
||||
def test_policy_check_unregistered(self):
|
||||
enforcer = glance.api.policy.Enforcer()
|
||||
context = glance.context.RequestContext(roles=[])
|
||||
|
||||
self.assertRaises(glance.api.policy.policy.PolicyNotRegistered,
|
||||
enforcer.check,
|
||||
context, 'wibble', {})
|
||||
|
||||
def test_policy_file_default_rules_default_location(self):
|
||||
enforcer = glance.api.policy.Enforcer()
|
||||
|
||||
|
22
releasenotes/notes/policy-in-code-7e0c6c070d32d136.yaml
Normal file
22
releasenotes/notes/policy-in-code-7e0c6c070d32d136.yaml
Normal file
@ -0,0 +1,22 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Policy defaults are now defined in code, as they already were in other
|
||||
OpenStack services. After upgrading there is no need to provide a
|
||||
``policy.json`` file (and you should not do so) unless you want to override
|
||||
the default policies, and only policies you want to override need be
|
||||
mentioned in the file. You should no longer rely on the ``default`` rule,
|
||||
and especially not the default value of the rule (which has been relaxed),
|
||||
to assign a non-default policy to rules not explicitly specified in the
|
||||
policy file.
|
||||
security:
|
||||
- |
|
||||
If the existing ``policy.json`` file relies on the ``default`` rule for
|
||||
some policies (i.e. not all policies are explicitly specified in the file)
|
||||
then the ``default`` rule must be explicitly set (e.g. to
|
||||
``"role:admin"``) in the file. The new default value for the ``default``
|
||||
rule is ``""``, whereas since the Queens release it has been
|
||||
``"role:admin"`` (prior to Queens it was ``"@"``, which allows everything).
|
||||
After upgrading to this release, the policy file should be replaced by one
|
||||
that overrides only policies that need to be different from the defaults,
|
||||
without relying on the ``default`` rule.
|
@ -66,6 +66,8 @@ glance.database.metadata_backend =
|
||||
|
||||
oslo.policy.enforcer =
|
||||
glance = glance.api.policy:get_enforcer
|
||||
oslo.policy.policies =
|
||||
glance = glance.policies:list_rules
|
||||
|
||||
glance.flows =
|
||||
api_image_import = glance.async_.flows.api_image_import:get_flow
|
||||
|
5
tox.ini
5
tox.ini
@ -69,6 +69,11 @@ whitelist_externals =
|
||||
commands =
|
||||
stestr run {posargs}
|
||||
|
||||
[testenv:genpolicy]
|
||||
basepython = python3
|
||||
commands =
|
||||
oslopolicy-sample-generator --config-file=etc/glance-policy-generator.conf
|
||||
|
||||
[testenv:gateonly]
|
||||
# NOTE(rosmaita): these tests catch configuration problems for some code
|
||||
# constants that must be maintained manually; we have them separated out
|
||||
|
Loading…
x
Reference in New Issue
Block a user