
Heat must have the domain of the user in order to validate the user for role assignment. Regardless of if roles are being changed or assigned, heat always requires and validates this data when heat receives a project template. This work persists the user domain when calls are made to CMS so that RDS can add this data to project heat templates. Change-Id: I5a8e72241e68dac730c3522d820a17d926fa3be8
187 lines
7.1 KiB
Python
Executable File
187 lines
7.1 KiB
Python
Executable File
"""Policy Engine For ORM."""
|
|
|
|
import logging
|
|
|
|
from . import qolicy
|
|
from orm.common.client.keystone.keystone_utils import tokens
|
|
from orm.common.orm_common.utils import api_error_utils as err_utils
|
|
from orm.common.orm_common.utils import dictator
|
|
from wsme.exc import ClientSideError
|
|
|
|
logger = logging.getLogger(__name__)
|
|
_ENFORCER = None
|
|
_POLICY_FILE = None
|
|
_TOKEN_CONF = None
|
|
|
|
|
|
def reset():
|
|
global _ENFORCER
|
|
if _ENFORCER:
|
|
_ENFORCER.clear()
|
|
_ENFORCER = None
|
|
|
|
global _POLICY_FILE
|
|
if _POLICY_FILE:
|
|
_POLICY_FILE = None
|
|
|
|
|
|
class EnforcerError(Exception):
|
|
"""An exception that receives *args and **kwargs, necessary for
|
|
Enforcer.enforce().
|
|
"""
|
|
def __init__(self, *args, **kwargs):
|
|
super(EnforcerError, self).__init__()
|
|
|
|
|
|
def _get_rules_from_file(path):
|
|
logger.debug('Reading policy file: {}'.format(path))
|
|
|
|
return qolicy.Rules.load_json(open(path, 'r').read(), 'default')
|
|
|
|
|
|
def init(policy_file, token_conf, default_rule=None):
|
|
"""Init an Enforcer class.
|
|
|
|
:param policy_file: Custom policy file to use.
|
|
:param default_rule: Default rule to use
|
|
:param token_conf: The Keystone utils token configuration
|
|
"""
|
|
logger.info('Initializing policy enforcer...')
|
|
|
|
global _ENFORCER
|
|
global _POLICY_FILE
|
|
global _TOKEN_CONF
|
|
if not _ENFORCER:
|
|
loaded_rules = _get_rules_from_file(policy_file)
|
|
_POLICY_FILE = policy_file
|
|
_TOKEN_CONF = token_conf
|
|
_ENFORCER = qolicy.Enforcer(None,
|
|
policy_file=None,
|
|
rules=loaded_rules,
|
|
default_rule=default_rule,
|
|
use_conf=False)
|
|
|
|
|
|
def reset_rules(overwrite=True, use_conf=False):
|
|
"""Reset rules based on the provided dict of rules.
|
|
|
|
:param rules: New rules to use. It should be an instance of dict.
|
|
:param overwrite: Whether to overwrite current rules or update them
|
|
with the new rules.
|
|
:param use_conf: Whether to reload rules from config file.
|
|
"""
|
|
if not _POLICY_FILE:
|
|
message = 'Policy file not set (did you call policy.init?)'
|
|
logger.error(message)
|
|
raise ValueError(message)
|
|
_ENFORCER.set_rules(_get_rules_from_file(_POLICY_FILE),
|
|
overwrite, use_conf)
|
|
|
|
|
|
def enforce(action, token, user, lcp_id=None, keystone_ep=None,
|
|
do_raise=True):
|
|
"""Verifies that the action is valid on the target in this context.
|
|
|
|
:param action: string representing the action to be checked
|
|
this should be colon separated for clarity.
|
|
i.e. ``compute:create_instance``,
|
|
``compute:attach_volume``,
|
|
``volume:attach_volume``
|
|
:param token: The token to validate
|
|
:param lcp_id: The ID of the LCP associated with the Keystone instance
|
|
with which the token was created
|
|
:param keystone_ep: The Keystone endpoint, in case we already have it
|
|
:param do_raise: if True (the default), raises Unauthorized (401);
|
|
if False, returns False
|
|
|
|
:raises EnforcerError if verification fails and do_raise is True.
|
|
|
|
:return: returns a non-False value (not necessarily "True") if
|
|
authorized, and the exact value False if not authorized and
|
|
do_raise is False.
|
|
"""
|
|
logger.debug('Enforcing policy - action: {}, token: {}, lcp_id: {}, '
|
|
'keystone_ep: {}'.format(action, token, lcp_id, keystone_ep))
|
|
# Re-read the rules, in case the policy file has changed
|
|
reset_rules()
|
|
|
|
# May raise EnforcerError, we'll let it propagate
|
|
result = _ENFORCER.enforce(action, {}, user, do_raise=do_raise,
|
|
exc=EnforcerError, action=action)
|
|
|
|
return result
|
|
|
|
|
|
def _is_authorization_enabled(app_conf):
|
|
return app_conf.authentication.enabled
|
|
|
|
|
|
def authorize(action, request, app_conf, keystone_ep=None):
|
|
"""Authorize a request.
|
|
|
|
:param action: The requested action, in the policy.json syntax
|
|
:param request: Pecan request object
|
|
:param app_conf: Application configuration
|
|
:param keystone_ep: Keystone endpoint, in case we already have it
|
|
|
|
:raises Unauthorized (401) in case anything fails in the authorization
|
|
process
|
|
"""
|
|
logger.info('Authorize...start')
|
|
|
|
token_to_validate = request.headers.get('X-Auth-Token')
|
|
lcp_id = request.headers.get('X-Auth-Region')
|
|
|
|
keystone_ep = keystone_ep if keystone_ep else (
|
|
request.headers.get('Keystone-Endpoint'))
|
|
try:
|
|
if _is_authorization_enabled(app_conf):
|
|
try:
|
|
# Set the service name for Nagios codes
|
|
dictator.soft_set('service_name', app_conf.server.name.upper())
|
|
user = tokens.get_token_user(token_to_validate, _TOKEN_CONF,
|
|
lcp_id, keystone_ep)
|
|
request.headers['X-RANGER-Client'] = user.user['name']
|
|
request.headers['X-RANGER-Owner'] = user.tenant['id']
|
|
request.headers['Keystone-Endpoint'] = user.auth_url
|
|
request.headers['User-Domain'] = user.domain
|
|
keystone_ep = user.auth_url
|
|
except Exception as ex:
|
|
user = None
|
|
request.headers['X-RANGER-Client'] = 'NA'
|
|
logger.exception(
|
|
"policy - Failed to get_token, using endpoint={}".format(
|
|
keystone_ep))
|
|
raise err_utils.get_error('N/A', status_code=401)
|
|
|
|
if token_to_validate is not None and lcp_id is not None and str(token_to_validate).strip() != '' and str(lcp_id).strip() != '':
|
|
logger.debug('Authorization: enforcing policy on token=[{}], lcp_id=[{}]'.format(token_to_validate, lcp_id))
|
|
enforce(action, token_to_validate, user, lcp_id, keystone_ep)
|
|
is_permitted = True
|
|
logger.debug('Authorization: policy check passed')
|
|
else:
|
|
logger.debug('Token=[{}] and/or Region=[{}] are empty/none.'.format(token_to_validate, lcp_id))
|
|
logger.info(
|
|
'INFO|CON{}AUTH004|One or more of the authentication headers are missing'.format(
|
|
dictator.get('service_name', 'ORM')))
|
|
# Enforce anyway, in case the policy for this is to always
|
|
# allow any user to perform this operation
|
|
enforce(action, token_to_validate, user)
|
|
is_permitted = True
|
|
else:
|
|
logger.debug('The authentication service is disabled. No authentication is needed.')
|
|
is_permitted = True
|
|
except ClientSideError as e:
|
|
logger.error('Fail to validate request. due to {}.'.format(str(e)))
|
|
raise err_utils.get_error('N/A', status_code=e.code)
|
|
except EnforcerError:
|
|
logger.error('The token is unauthorized according to the policy')
|
|
is_permitted = False
|
|
except Exception as e:
|
|
logger.error('Fail to validate request. due to {}.'.format(str(e)))
|
|
is_permitted = False
|
|
|
|
logger.info('Authorize...end')
|
|
if not is_permitted:
|
|
raise err_utils.get_error('N/A', status_code=401)
|