diff options
author | Zuul <zuul@review.opendev.org> | 2019-07-18 19:47:31 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2019-07-18 19:47:31 +0000 |
commit | 3183b3d2fc28e36efab7c19f13abe7146cf6b93b (patch) | |
tree | 510ad792bf21f67404ef3ce56bb6e5294217ffe8 /keystonemiddleware/auth_token | |
parent | 06fc3e42d79ebe81a8cd4c0a482ef67223599ad2 (diff) | |
parent | 5f093bf5ee9f8ed201f01bab9c9afbde0423df07 (diff) | |
download | keystonemiddleware-3183b3d2fc28e36efab7c19f13abe7146cf6b93b.tar.gz |
Merge "Add validation of app cred access rules"7.0.0
Diffstat (limited to 'keystonemiddleware/auth_token')
-rw-r--r-- | keystonemiddleware/auth_token/__init__.py | 74 | ||||
-rw-r--r-- | keystonemiddleware/auth_token/_identity.py | 5 | ||||
-rw-r--r-- | keystonemiddleware/auth_token/_opts.py | 4 |
3 files changed, 81 insertions, 2 deletions
diff --git a/keystonemiddleware/auth_token/__init__.py b/keystonemiddleware/auth_token/__init__.py index dcf166b..b29b63a 100644 --- a/keystonemiddleware/auth_token/__init__.py +++ b/keystonemiddleware/auth_token/__init__.py @@ -218,6 +218,7 @@ object is stored. """ import copy +import re from keystoneauth1 import access from keystoneauth1 import adapter @@ -277,6 +278,26 @@ def list_opts(): return [(g, copy.deepcopy(o)) for g, o in AUTH_TOKEN_OPTS] +def _path_matches(request_path, path_pattern): + # The fnmatch module doesn't provide the ability to match * versus **, + # so convert to regex. + token_regex = (r'(?P<tag>{[^}]*})|' # {tag} # nosec + '(?P<wild>\*(?=$|[^\*]))|' # * + '(?P<rec_wild>\*\*)|' # ** + '(?P<literal>[^{}\*])') # anything else + path_regex = '' + for match in re.finditer(token_regex, path_pattern): + token = match.groupdict() + if token['tag'] or token['wild']: + path_regex += '[^\/]+' + if token['rec_wild']: + path_regex += '.*' + if token['literal']: + path_regex += token['literal'] + path_regex = r'^%s$' % path_regex + return re.match(path_regex, request_path) + + class _BIND_MODE(object): DISABLED = 'disabled' PERMISSIVE = 'permissive' @@ -301,13 +322,15 @@ class BaseAuthProtocol(object): log=_LOG, enforce_token_bind=_BIND_MODE.PERMISSIVE, service_token_roles=None, - service_token_roles_required=False): + service_token_roles_required=False, + service_type=None): self.log = log self._app = app self._enforce_token_bind = enforce_token_bind self._service_token_roles = set(service_token_roles or []) self._service_token_roles_required = service_token_roles_required self._service_token_warning_emitted = False + self._service_type = service_type @webob.dec.wsgify(RequestClass=_request._AuthTokenRequest) def __call__(self, req): @@ -388,6 +411,8 @@ class BaseAuthProtocol(object): allow_expired=allow_expired) self._validate_token(user_auth_ref, allow_expired=allow_expired) + if user_auth_ref.version != 'v2.0': + self.validate_allowed_request(request, data['token']) if not request.service_token: self._confirm_token_bind(user_auth_ref, request) except ksm_exceptions.InvalidToken: @@ -516,6 +541,53 @@ class BaseAuthProtocol(object): {'bind_type': bind_type, 'identifier': identifier}) self._invalid_user_token() + def validate_allowed_request(self, request, token): + self.log.debug("Validating token access rules against request") + app_cred = token.get('application_credential') + if not app_cred: + return + access_rules = app_cred.get('access_rules') + if access_rules is None: + return + if hasattr(self, '_conf'): + my_service_type = self._conf.get('service_type') + else: + my_service_type = self._service_type + if not my_service_type: + self.log.warning('Cannot validate request with restricted' + ' access rules. Set service_type in' + ' [keystone_authtoken] to allow access rule' + ' validation.') + raise ksm_exceptions.InvalidToken(_('Token authorization failed')) + # token can always be validated regardless of access rules + if (my_service_type == 'identity' and + request.method == 'GET' and + request.path.endswith('/v3/auth/tokens')): + return + catalog = token['catalog'] + # validate service type is in catalog + catalog_svcs = [s for s in catalog if s['type'] == my_service_type] + if len(catalog_svcs) == 0: + self.log.warning('Cannot validate request with restricted' + ' access rules. service_type in' + ' [keystone_authtoken] is not a valid service' + ' type in the catalog.') + raise ksm_exceptions.InvalidToken(_('Token authorization failed')) + if request.service_token: + # The request may not match an allowed request, but the presence + # of the service token indicates this is a chain of requests and + # hence this request was not user-facing + return + for access_rule in access_rules: + method = access_rule['method'] + path = access_rule['path'] + service = access_rule['service'] + if request.method == method and \ + service == my_service_type and \ + _path_matches(request.path, path): + return + raise ksm_exceptions.InvalidToken(_('Token authorization failed')) + class AuthProtocol(BaseAuthProtocol): """Middleware that handles authenticating client calls.""" diff --git a/keystonemiddleware/auth_token/_identity.py b/keystonemiddleware/auth_token/_identity.py index a912e16..49bdf9a 100644 --- a/keystonemiddleware/auth_token/_identity.py +++ b/keystonemiddleware/auth_token/_identity.py @@ -21,6 +21,8 @@ from keystonemiddleware.auth_token import _auth from keystonemiddleware.auth_token import _exceptions as ksm_exceptions from keystonemiddleware.i18n import _ +ACCESS_RULES_SUPPORT = '1' + class _RequestStrategy(object): @@ -69,7 +71,8 @@ class _V3RequestStrategy(_RequestStrategy): auth_ref = self._client.tokens.validate( token, include_catalog=self._include_service_catalog, - allow_expired=allow_expired) + allow_expired=allow_expired, + access_rules_support=ACCESS_RULES_SUPPORT) if not auth_ref: msg = _('Failed to fetch token data from identity server') diff --git a/keystonemiddleware/auth_token/_opts.py b/keystonemiddleware/auth_token/_opts.py index b551407..f16d3f8 100644 --- a/keystonemiddleware/auth_token/_opts.py +++ b/keystonemiddleware/auth_token/_opts.py @@ -178,6 +178,10 @@ _OPTS = [ ' service tokens pass that don\'t pass the service_token_roles' ' check as valid. Setting this true will become the default' ' in a future release and should be enabled if possible.'), + cfg.StrOpt('service_type', + help='The name or type of the service as it appears in the' + ' service catalog. This is used to validate tokens that have' + ' restricted access rules.'), ] |