Dynamic policy file discovery
Patrole should eventually support other services like Heat
and Murano, not just the Big Tent services included in Tempest.
Patrole then should be able to dynamically discover custom
policy files. While the solution this commit implements is
not perfect, it will allow more services' policy file to
be discovered by Patrole. The policy files will still
have to be located on the same host as Patrole.
This commit removes the service-specific policy path
CONF options in favor of a new CONF option called
``[rbac] custom_policy_files`` which is a ListOpt that
includes paths for each custom policy file. Each
policy path assumes that the service name is included in
the path. The paths should be ordered by precedence, with
high-priority paths before low-priority paths. The first
path that is found to contain the service's policy file
will be used.
This commit refactors unit tests and rbac_policy_parser
as needed to work with the changes.
Change-Id: Ia929b77223b54906888af6cd324f0cfa0fafda8f
Implements blueprint: dynamic-policy-file-discovery
diff --git a/patrole_tempest_plugin/rbac_policy_parser.py b/patrole_tempest_plugin/rbac_policy_parser.py
index 17a626c..41871cf 100644
--- a/patrole_tempest_plugin/rbac_policy_parser.py
+++ b/patrole_tempest_plugin/rbac_policy_parser.py
@@ -58,26 +58,27 @@
the custom policy file over the default policy implementation is
prioritized.
- :param project_id: type uuid
- :param user_id: type uuid
- :param service: type string
- :param path: type string
+ :param uuid project_id: project_id of object performing API call
+ :param uuid user_id: user_id of object performing API call
+ :param string service: service of the policy file
+ :param dict extra_target_data: dictionary containing additional object
+ data needed by oslo.policy to validate generic checks
"""
if extra_target_data is None:
extra_target_data = {}
- # First check if the service is valid.
self.validate_service(service)
- # Use default path in /etc/<service_name/policy.json if no path
- # is provided.
- path = getattr(CONF.rbac, '%s_policy_file' % str(service), None)
- if not path:
- LOG.info("No config option found for %s,"
- " using default path", str(service))
- path = os.path.join('/etc', service, 'policy.json')
- self.path = path
+ # Prioritize dynamically searching for policy files over relying on
+ # deprecated service-specific policy file locations.
+ if CONF.rbac.custom_policy_files:
+ self.discover_policy_files()
+ self.path = self.policy_files.get(service)
+ else:
+ self.path = getattr(CONF.rbac, '%s_policy_file' % str(service),
+ None)
+
self.rules = policy.Rules.load(self._get_policy_data(service),
'default')
self.project_id = project_id
@@ -86,7 +87,7 @@
@classmethod
def validate_service(cls, service):
- """Validate whether the service passed to ``init`` exists."""
+ """Validate whether the service passed to ``__init__`` exists."""
service = service.lower().strip() if service else None
# Cache the list of available services in memory to avoid needlessly
@@ -102,6 +103,19 @@
raise rbac_exceptions.RbacInvalidService(
"%s is NOT a valid service." % service)
+ @classmethod
+ def discover_policy_files(cls):
+ # Dynamically discover the policy file for each service in
+ # ``cls.available_services``. Pick the first ``candidate_path`` found
+ # out of the potential paths in ``CONF.rbac.custom_policy_files``.
+ if not hasattr(cls, 'policy_files'):
+ cls.policy_files = {}
+ for service in cls.available_services:
+ for candidate_path in CONF.rbac.custom_policy_files:
+ if os.path.isfile(candidate_path % service):
+ cls.policy_files.setdefault(service,
+ candidate_path % service)
+
def allowed(self, rule_name, role):
is_admin_context = self._is_admin_context(role)
is_allowed = self._allowed(
@@ -115,8 +129,8 @@
mgr_policy_data = {}
policy_data = {}
- # Check whether policy file exists.
- if os.path.isfile(self.path):
+ # Check whether policy file exists and attempt to read it.
+ if self.path and os.path.isfile(self.path):
try:
with open(self.path, 'r') as policy_file:
file_policy_data = policy_file.read()
@@ -158,8 +172,11 @@
elif mgr_policy_data:
policy_data = mgr_policy_data
else:
- error_message = 'Policy file for {0} service neither found in '\
- 'code nor at {1}.'.format(service, self.path)
+ error_message = (
+ 'Policy file for {0} service neither found in code nor at {1}.'
+ .format(service, [loc % service for loc in
+ CONF.rbac.custom_policy_files])
+ )
raise rbac_exceptions.RbacParsingException(error_message)
try: