Add oslopolicy-validator tool

As requested in the referenced RFE bug, this is a validator tool
similar to the oslo.config validator tool that operators can use to
look for basic errors in their policy files.

It's very similar to the redundant rule tool, but I decided not to
combine them because I feel like the target use cases are enough
different to warrant separate tools. Specifically, the redundant
rule tool is looking for perfectly valid rules that just happen to
be unnecessary. The validator is looking for errors in the policy
file. While it's unlikely someone looking for redundant rules wouldn't
also want to know if there is something broken in their policy file,
it's likely that someone just looking to sanity check their policy
before deployment wouldn't want to see a bunch of messages about
redundant rules that won't cause any problems.

Change-Id: I799a754aceac080c11baffd7ff635b2a9cb825f7
Closes-Bug: 1853038
This commit is contained in:
Ben Nemec 2020-01-15 22:10:16 +00:00
parent 719435f732
commit 283768e910
5 changed files with 163 additions and 1 deletions

View File

@ -151,3 +151,43 @@ For more information regarding the options supported by this tool:
.. code-block:: bash
oslopolicy-list-redundant --help
oslopolicy_validator
====================
The ``oslopolicy-validator`` tool can be used to perform basic sanity checks
against a policy file. It will detect the following problems:
* A missing policy file
* Rules which have invalid syntax
* Rules which reference non-existent other rules
* Rules which form a cyclical reference with another rule
* Rules which do not exist in the specified namespace
This tool does very little validation of the content of the rules. Other tools,
such as ``oslopolicy-checker``, should be used to check that rules do what is
intended.
``oslopolicy-validator`` exits with a ``0`` return code on success and ``1`` on
failure.
.. note:: At this time the policy validator can only handle single policy
files, not policy dirs.
Examples
--------
Validate the policy file used for Keystone:
.. code-block:: bash
oslopolicy-validator --config-file /etc/keystone/keystone.conf --namespace keystone
Sample output from a failed validation::
$ oslopolicy-validator --config-file keystone.conf --namespace keystone
WARNING:oslo_policy.policy:Policies ['foo', 'bar'] are part of a cyclical reference.
Invalid rules found
Failed to parse rule: (role:admin and system_scope:all) or (role:foo and oken.domain.id:%(target.user.domain_id)s))
Unknown rule found in policy file: foo
Unknown rule found in policy file: bar

View File

@ -328,12 +328,70 @@ def _list_redundant(namespace):
enforcer.load_rules()
for name, file_rule in enforcer.file_rules.items():
reg_rule = enforcer.registered_rules.get(name, None)
reg_rule = enforcer.registered_rules.get(name)
if reg_rule:
if file_rule == reg_rule:
print(reg_rule)
def _validate_policy(namespace):
"""Perform basic sanity checks on a policy file
Checks for the following errors in the configured policy file:
* A missing policy file
* Rules which have invalid syntax
* Rules which reference non-existent other rules
* Rules which form a cyclical reference with another rule
* Rules which do not exist in the specified namespace
:param namespace: The name under which the oslo.policy enforcer is
registered.
:returns: 0 if all policies validated correctly, 1 if not.
"""
return_code = 0
enforcer = _get_enforcer(namespace)
# NOTE(bnemec): We don't want to see policy deprecation warnings in the
# output of this tool. They tend to overwhelm the output that the user
# actually cares about. If we check for deprecated rules in this tool,
# we need to do it another way.
enforcer.suppress_deprecation_warnings = True
# Disable logging from the parser code. We'll be printing any errors we
# find below.
logging.disable(logging.ERROR)
# Ensure that files have been parsed
enforcer.load_rules()
if enforcer._informed_no_policy_file:
print('Configured policy file "%s" not found' % enforcer.policy_file)
# If the policy file is completely missing then the rest of our checks
# don't make sense.
return 1
# Re-enable logging so we get messages for things like cyclical references
logging.disable(logging.NOTSET)
result = enforcer.check_rules()
if not result:
print('Invalid rules found')
return_code = 1
# TODO(bnemec): Allow this to handle policy_dir
with open(cfg.CONF.oslo_policy.policy_file) as f:
unparsed_policies = yaml.safe_load(f.read())
for name, file_rule in enforcer.file_rules.items():
reg_rule = enforcer.registered_rules.get(name)
if reg_rule is None:
print('Unknown rule found in policy file:', name)
return_code = 1
# If a rule has invalid syntax it will be forced to '!'. If the literal
# rule from the policy file isn't '!' then this means there was an
# error parsing it.
if str(enforcer.rules[name]) == '!' and unparsed_policies[name] != '!':
print('Failed to parse rule:', unparsed_policies[name])
return_code = 1
return return_code
def on_load_failure_callback(*args, **kwargs):
raise
@ -423,3 +481,12 @@ def list_redundant(args=None):
conf(args)
_check_for_namespace_opt(conf)
_list_redundant(conf.namespace)
def validate_policy(args=None):
logging.basicConfig(level=logging.WARN)
conf = cfg.CONF
conf.register_cli_opts(ENFORCER_OPTS)
conf.register_opts(ENFORCER_OPTS)
conf(args)
sys.exit(_validate_policy(conf.namespace))

View File

@ -751,3 +751,51 @@ class GetEnforcerTestCase(base.PolicyBaseTestCase):
mock_instance.__contains__.return_value = False
mock_manager.return_value = mock_instance
self.assertRaises(KeyError, generator._get_enforcer, 'nonexistent')
class ValidatorTestCase(base.PolicyBaseTestCase):
def _get_test_enforcer(self):
test_rules = [policy.RuleDefault('foo', 'foo:bar=baz'),
policy.RuleDefault('bar', 'bar:foo=baz')]
enforcer = policy.Enforcer(self.conf)
enforcer.register_defaults(test_rules)
return enforcer
def _test_policy(self, rule, success=False, missing_file=False):
policy_file = self.get_config_file_fullname('test.yaml')
if missing_file:
policy_file = 'bogus.yaml'
self.create_config_file('test.yaml', rule)
self.create_config_file('test.conf',
'[oslo_policy]\npolicy_file=%s' % policy_file)
# Reparse now that we've created our configs
self.conf(args=['--config-dir', self.config_dir])
with mock.patch('oslo_policy.generator._get_enforcer') as ge:
ge.return_value = self._get_test_enforcer()
result = generator._validate_policy('test')
if success:
self.assertEqual(0, result)
else:
self.assertEqual(1, result)
def test_success(self):
self._test_policy('foo: rule:bar', success=True)
def test_cyclical_reference(self):
self._test_policy('foo: rule:bar\nbar: rule:foo')
def test_invalid_syntax(self):
self._test_policy('foo: (bar))')
def test_false_okay(self):
self._test_policy('foo: !', success=True)
def test_reference_nonexistent(self):
self._test_policy('foo: rule:baz')
def test_nonexistent(self):
self._test_policy('baz: rule:foo')
def test_missing_policy_file(self):
self._test_policy('', missing_file=True)

View File

@ -0,0 +1,6 @@
---
features:
- |
A new tool, ``oslopolicy-validator``, has been added. It allows deployers
to easily run basic sanity checks against their policy files. See the
documentation for full details.

View File

@ -35,6 +35,7 @@ console_scripts =
oslopolicy-policy-generator = oslo_policy.generator:generate_policy
oslopolicy-list-redundant = oslo_policy.generator:list_redundant
oslopolicy-policy-upgrade = oslo_policy.generator:upgrade_policy
oslopolicy-validator = oslo_policy.generator:validate_policy
oslo.policy.rule_checks =
http = oslo_policy._external:HttpCheck