summaryrefslogtreecommitdiff
path: root/keystonemiddleware/auth_token
diff options
context:
space:
mode:
authorColleen Murphy <colleen@gazlene.net>2019-01-26 23:06:00 +0100
committerColleen Murphy <colleen.murphy@suse.de>2019-07-15 16:05:59 -0700
commit5f093bf5ee9f8ed201f01bab9c9afbde0423df07 (patch)
tree90e46bfea194cc999b03e367cb9eb2572dcf011d /keystonemiddleware/auth_token
parent2d3765ed565aba3b9793efcfc5c2d1ea534f1a0d (diff)
downloadkeystonemiddleware-5f093bf5ee9f8ed201f01bab9c9afbde0423df07.tar.gz
Add validation of app cred access rules
This commit adds a validation step in the auth_token middleware to check for the presence of an access_rules attribute in an application credential token and to validate the request against the permissions granted for that token. During token validation it sends a header to keystone to indicate that it is capable of validating these access rules, and not providing this header for a token like this would result in the token failing validation. This disregards access rules for a service request made by a service on behalf of a user, such as nova making a request to glance, because such a request is not under the control of the user and is not expected to be explicitly allowed in the access rules. bp whitelist-extension-for-app-creds Depends-On: https://review.opendev.org/670377 Change-Id: I185e0541d5df538d74edadf9976b3034a2470c88
Diffstat (limited to 'keystonemiddleware/auth_token')
-rw-r--r--keystonemiddleware/auth_token/__init__.py74
-rw-r--r--keystonemiddleware/auth_token/_identity.py5
-rw-r--r--keystonemiddleware/auth_token/_opts.py4
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 4c10521..3967c23 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.'),
]