From 6a3fa87cde3a1a273c49d515d8139df14a69cf9a Mon Sep 17 00:00:00 2001 From: Tobias Urdin Date: Mon, 25 Apr 2022 08:31:33 +0000 Subject: [PATCH] Pass client IP to keystoneauth1 session This passes the client IP to the keystoneauth1 Session's original_ip parameter. This sets the Forwarder HTTP header so that when the request lands in Keystone the request can actually be interpreted who made the request and not only that it was proxied by Horizon. Forwarded: for=100.64.10.1;by=openstack_auth keystoneauth1/4.4.0 python-requests/2.25.1 CPython/3.6.8 In the above example header the 100.64.10.1 is the client IP that is sent from a load balancer in the X-Forwarded-For header while the actual REMOTE_ADDR in the HTTP request is the load balancers IP address. Change-Id: I52da9dcd7fb6b1ac46852718f285795628121e26 --- openstack_auth/backend.py | 12 ++++++++---- openstack_auth/plugin/base.py | 27 +++++++++++++++++++-------- openstack_auth/views.py | 6 ++++-- 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/openstack_auth/backend.py b/openstack_auth/backend.py index 29b3b55a5f..a238ccced5 100644 --- a/openstack_auth/backend.py +++ b/openstack_auth/backend.py @@ -111,22 +111,27 @@ class KeystoneBackend(object): plugin, unscoped_auth = self._get_auth_backend(auth_url, **kwargs) + client_ip = utils.get_client_ip(request) + session = utils.get_session(original_ip=client_ip) + # the recent project id a user might have set in a cookie recent_project = None if request: # Grab recent_project found in the cookie, try to scope # to the last project used. recent_project = request.COOKIES.get('recent_project') - unscoped_auth_ref = plugin.get_access_info(unscoped_auth) + unscoped_auth_ref = plugin.get_access_info(unscoped_auth, + session=session) # Check expiry for our unscoped auth ref. self._check_auth_expiry(unscoped_auth_ref) domain_name = kwargs.get('user_domain_name', None) domain_auth, domain_auth_ref = plugin.get_domain_scoped_auth( - unscoped_auth, unscoped_auth_ref, domain_name) + unscoped_auth, unscoped_auth_ref, domain_name, session=session) scoped_auth, scoped_auth_ref = plugin.get_project_scoped_auth( - unscoped_auth, unscoped_auth_ref, recent_project=recent_project) + unscoped_auth, unscoped_auth_ref, recent_project=recent_project, + session=session) # Abort if there are no projects for this user and a valid domain # token has not been obtained @@ -207,7 +212,6 @@ class KeystoneBackend(object): request.session.set_expiry(session_time) keystone_client_class = utils.get_keystone_client().Client - session = utils.get_session() scoped_client = keystone_client_class(session=session, auth=scoped_auth) diff --git a/openstack_auth/plugin/base.py b/openstack_auth/plugin/base.py index 28f90377d6..00ffe020ab 100644 --- a/openstack_auth/plugin/base.py +++ b/openstack_auth/plugin/base.py @@ -99,17 +99,19 @@ class BasePlugin(object, metaclass=abc.ABCMeta): msg = _('Unable to retrieve authorized domains.') raise exceptions.KeystoneRetrieveDomainsException(msg) - def get_access_info(self, keystone_auth): + def get_access_info(self, keystone_auth, session=None): """Get the access info from an unscoped auth This function provides the base functionality that the plugins will use to authenticate and get the access info object. :param keystone_auth: keystoneauth1 identity plugin + :param session: keystoneauth1 session to use otherwise gets one :raises: exceptions.KeystoneAuthException on auth failure :returns: keystoneclient.access.AccessInfo """ - session = utils.get_session() + if session is None: + session = utils.get_session() try: unscoped_auth_ref = keystone_auth.get_access(session) @@ -140,7 +142,7 @@ class BasePlugin(object, metaclass=abc.ABCMeta): return unscoped_auth_ref def get_project_scoped_auth(self, unscoped_auth, unscoped_auth_ref, - recent_project=None): + recent_project=None, session=None): """Get the project scoped keystone auth and access info This function returns a project scoped keystone token plugin @@ -149,10 +151,13 @@ class BasePlugin(object, metaclass=abc.ABCMeta): :param unscoped_auth: keystone auth plugin :param unscoped_auth_ref: keystoneclient.access.AccessInfo` or None. :param recent_project: project that we should try to scope to + :param session: keystoneauth1 session to use otherwise gets one :return: keystone token auth plugin, AccessInfo object """ + if session is None: + session = utils.get_session() + auth_url = unscoped_auth.auth_url - session = utils.get_session() projects = self.list_projects( session, unscoped_auth, unscoped_auth_ref) @@ -187,7 +192,7 @@ class BasePlugin(object, metaclass=abc.ABCMeta): return scoped_auth, scoped_auth_ref def get_domain_scoped_auth(self, unscoped_auth, unscoped_auth_ref, - domain_name=None): + domain_name=None, session=None): """Get the domain scoped keystone auth and access info This function returns a domain scoped keystone token plugin @@ -196,9 +201,12 @@ class BasePlugin(object, metaclass=abc.ABCMeta): :param unscoped_auth: keystone auth plugin :param unscoped_auth_ref: keystoneclient.access.AccessInfo` or None. :param domain_name: domain that we should try to scope to + :param session: keystoneauth1 session to use otherwise gets one :return: keystone token auth plugin, AccessInfo object """ - session = utils.get_session() + if session is None: + session = utils.get_session() + auth_url = unscoped_auth.auth_url if domain_name: @@ -235,7 +243,7 @@ class BasePlugin(object, metaclass=abc.ABCMeta): return domain_auth, domain_auth_ref def get_system_scoped_auth(self, unscoped_auth, unscoped_auth_ref, - system_scope): + system_scope, session=None): """Get the system scoped keystone auth and access info This function returns a system scoped keystone token plugin @@ -244,9 +252,12 @@ class BasePlugin(object, metaclass=abc.ABCMeta): :param unscoped_auth: keystone auth plugin :param unscoped_auth_ref: keystoneclient.access.AccessInfo` or None. :param system_scope: system that we should try to scope to + :param session: keystoneauth1 session to use otherwise gets one :return: keystone token auth plugin, AccessInfo object """ - session = utils.get_session() + if session is None: + session = utils.get_session() + auth_url = unscoped_auth.auth_url system_auth = None diff --git a/openstack_auth/views.py b/openstack_auth/views.py index 5ec08581ce..20e65e43b3 100644 --- a/openstack_auth/views.py +++ b/openstack_auth/views.py @@ -267,7 +267,8 @@ def switch(request, tenant_id, redirect_field_name=auth.REDIRECT_FIELD_NAME): tenant_id, request.user.username) endpoint, __ = utils.fix_auth_url_version_prefix(request.user.endpoint) - session = utils.get_session() + client_ip = utils.get_client_ip(request) + session = utils.get_session(original_ip=client_ip) # Keystone can be configured to prevent exchanging a scoped token for # another token. Always use the unscoped token for requesting a # scoped token. @@ -421,7 +422,8 @@ def switch_system_scope(request, redirect_field_name=auth.REDIRECT_FIELD_NAME): LOG.debug('Switching to system scope for user "%s".', request.user.username) endpoint, __ = utils.fix_auth_url_version_prefix(request.user.endpoint) - session = utils.get_session() + client_ip = utils.get_client_ip(request) + session = utils.get_session(original_ip=client_ip) # Keystone can be configured to prevent exchanging a scoped token for # another token. Always use the unscoped token for requesting a # scoped token.