summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/cli/index.rst40
-rw-r--r--oslo_policy/generator.py69
-rw-r--r--oslo_policy/tests/test_generator.py48
-rw-r--r--releasenotes/notes/policy-file-validator-906d5cff864a2d51.yaml6
-rw-r--r--setup.cfg1
5 files changed, 163 insertions, 1 deletions
diff --git a/doc/source/cli/index.rst b/doc/source/cli/index.rst
index c7023de..bef7c99 100644
--- a/doc/source/cli/index.rst
+++ b/doc/source/cli/index.rst
@@ -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
diff --git a/oslo_policy/generator.py b/oslo_policy/generator.py
index 48a9972..40f374d 100644
--- a/oslo_policy/generator.py
+++ b/oslo_policy/generator.py
@@ -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))
diff --git a/oslo_policy/tests/test_generator.py b/oslo_policy/tests/test_generator.py
index 86003fe..af6398f 100644
--- a/oslo_policy/tests/test_generator.py
+++ b/oslo_policy/tests/test_generator.py
@@ -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)
diff --git a/releasenotes/notes/policy-file-validator-906d5cff864a2d51.yaml b/releasenotes/notes/policy-file-validator-906d5cff864a2d51.yaml
new file mode 100644
index 0000000..5e41c62
--- /dev/null
+++ b/releasenotes/notes/policy-file-validator-906d5cff864a2d51.yaml
@@ -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.
diff --git a/setup.cfg b/setup.cfg
index 700f8c1..056fb14 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -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