summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBen Nemec <bnemec@redhat.com>2020-01-15 22:10:16 +0000
committerBen Nemec <bnemec@redhat.com>2020-06-26 14:55:26 +0000
commit283768e910257f81b599d2c409bf4a021bc50eae (patch)
treedda611ba471f8f4c474887bc66bcf2a3aa6f5217
parent719435f7323e55ee6b890f622d0fe9533b635adb (diff)
downloadoslo-policy-283768e910257f81b599d2c409bf4a021bc50eae.tar.gz
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
-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