Răsfoiți Sursa

Add support for XFF header in Knox and SAML logins (#3325)

Co-authored-by: Athithyaa Selvam <aselvam@cloudera.com>
Athithyaa Selvam 2 ani în urmă
părinte
comite
74e93fedd0

+ 3 - 3
apps/beeswax/gen-py/TCLIService/TCLIService.py

@@ -222,15 +222,15 @@ def add_xff_header(func):
     configuration = None
     if args[1].__dict__ is not None and 'configuration' in args[1].__dict__:
       configuration = args[1].__dict__['configuration']
-    if configuration is not None and 'HTTP-X-FORWARDED-FOR' in configuration:
-        xff = configuration.get('HTTP-X-FORWARDED-FOR')
+    if configuration is not None and 'X-Forwarded-For' in configuration:
+        xff = configuration.get('X-Forwarded-For')
     try:
         if hasattr(self._oprot.trans, 'TFramedTransport'):
           trans_client = self._oprot.trans._TFramedTransport__trans
         else:
           trans_client = self._oprot.trans._TBufferedTransport__trans
 
-        trans_client.setCustomHeaders({'HTTP-X-FORWARDED-FOR': xff})
+        trans_client.setCustomHeaders({'X-Forwarded-For': xff})
     except AttributeError as e:
         LOG.error('Could not set HTTP-X-FORWARDED-FOR header: %s' % smart_str(e))
 

+ 7 - 7
apps/beeswax/src/beeswax/server/hive_server2_lib.py

@@ -30,7 +30,7 @@ from TCLIService.ttypes import TOpenSessionReq, TGetTablesReq, TFetchResultsReq,
   TGetCrossReferenceReq, TGetPrimaryKeysReq
 
 from desktop.lib import python_util, thrift_util
-from desktop.conf import DEFAULT_USER, USE_THRIFT_HTTP_JWT
+from desktop.conf import DEFAULT_USER, USE_THRIFT_HTTP_JWT, ENABLE_XFF_FOR_HIVE_IMPALA
 
 from beeswax import conf as beeswax_conf, hive_site
 from beeswax.hive_site import hiveserver2_use_ssl
@@ -677,9 +677,9 @@ class HiveServerClient(object):
 
     if self.query_server['server_name'] == 'beeswax': # All the time
       kwargs['configuration'].update({'hive.server2.proxy.user': user.username})
-      xff_header = json.loads(user.userprofile.json_data).get('HTTP-X-FORWARDED-FOR', None)
-      if xff_header:
-        kwargs['configuration'].update({'HTTP-X-FORWARDED-FOR': xff_header})
+      xff_header = json.loads(user.userprofile.json_data).get('X-Forwarded-For', None)
+      if xff_header and ENABLE_XFF_FOR_HIVE_IMPALA.get():
+        kwargs['configuration'].update({'X-Forwarded-For': xff_header})
 
     if self.query_server['server_name'] == 'hplsql' or interpreter['dialect'] == 'hplsql': # All the time
       kwargs['configuration'].update({'hive.server2.proxy.user': user.username, 'set:hivevar:mode': 'HPLSQL'})
@@ -692,9 +692,9 @@ class HiveServerClient(object):
 
     if self.query_server.get('dialect') == 'impala' and self.query_server['SESSION_TIMEOUT_S'] > 0:
       kwargs['configuration'].update({'idle_session_timeout': str(self.query_server['SESSION_TIMEOUT_S'])})
-      xff_header = json.loads(user.userprofile.json_data).get('HTTP-X-FORWARDED-FOR', None)
-      if xff_header:
-        kwargs['configuration'].update({'HTTP-X-FORWARDED-FOR': xff_header})
+      xff_header = json.loads(user.userprofile.json_data).get('X-Forwarded-For', None)
+      if xff_header and ENABLE_XFF_FOR_HIVE_IMPALA.get():
+        kwargs['configuration'].update({'X-Forwarded-For': xff_header})
 
     LOG.info('Opening %s thrift session for user %s' % (self.query_server['server_name'], user.username))
 

+ 3 - 0
desktop/conf.dist/hue.ini

@@ -186,6 +186,9 @@ http_500_debug_mode=false
 # Enable X-Forwarded-Host header if the load balancer requires it.
 ## use_x_forwarded_host=true
 
+# Enable X-Forwarded-For header if hive/impala requires it.
+## enable_xff_for_hive_impala=true
+
 # Support for HTTPS termination at the load-balancer level with SECURE_PROXY_SSL_HEADER.
 ## secure_proxy_ssl_header=false
 

+ 3 - 0
desktop/conf/pseudo-distributed.ini.tmpl

@@ -191,6 +191,9 @@
   # Enable X-Forwarded-Host header if the load balancer requires it.
   ## use_x_forwarded_host=true
 
+  # Enable X-Forwarded-For header if hive/impala requires it.
+  ## enable_xff_for_hive_impala=true
+
   # Support for HTTPS termination at the load-balancer level with SECURE_PROXY_SSL_HEADER.
   ## secure_proxy_ssl_header=false
 

+ 7 - 0
desktop/core/src/desktop/auth/backend.py

@@ -238,6 +238,13 @@ def force_username_case(username):
     username = username.upper()
   return username
 
+def knox_login_headers(request):
+  userprofile = get_profile(request.user)
+  try:
+    userprofile.update_data({'X-Forwarded-For': request.META['HTTP_X_FORWARDED_FOR']})
+    userprofile.save()
+  except Exception:
+    LOG.error("X-FORWARDED-FOR header not found")
 
 class DesktopBackendBase(object):
   """

+ 3 - 2
desktop/core/src/desktop/auth/views.py

@@ -49,7 +49,7 @@ from desktop.lib import fsmanager
 from desktop.lib.django_util import render, login_notrequired, JsonResponse
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.log.access import access_log, access_warn, last_access_map
-from desktop.views import samlgroup_check
+from desktop.views import samlgroup_check, saml_login_headers
 from desktop.settings import LOAD_BALANCER_COOKIE
 from django.utils.encoding import smart_str
 
@@ -165,7 +165,7 @@ def dt_login(request, from_modal=False):
           userprofile.creation_method = UserProfile.CreationMethod.EXTERNAL.name
         userprofile.update_data({'auth_backend': user.backend})
         try:
-          userprofile.update_data({'HTTP-X-FORWARDED-FOR': request.META['HTTP_X_FORWARDED_FOR']})
+          userprofile.update_data({'X-Forwarded-For': request.META['HTTP_X_FORWARDED_FOR']})
         except KeyError as e:
           LOG.error('HTTP-X-FORWARDED-FOR header not found: %s' %smart_str(e))
         userprofile.save()
@@ -211,6 +211,7 @@ def dt_login(request, from_modal=False):
 
   if 'SAML2Backend' in backend_names:
     request.session['samlgroup_permitted_flag'] = samlgroup_check(request)
+    saml_login_headers(request)
 
   renderable_path = 'login.mako'
   if from_modal:

+ 6 - 0
desktop/core/src/desktop/conf.py

@@ -544,6 +544,12 @@ USE_X_FORWARDED_HOST = Config(
   type=coerce_bool,
   dynamic_default=is_lb_enabled)
 
+ENABLE_XFF_FOR_HIVE_IMPALA = Config(
+  key="enable_xff_for_hive_impala",
+  help=_("Enable X-Forwarded-For header if the hive/impala requires it."),
+  type=coerce_bool,
+  default=False)
+
 SECURE_PROXY_SSL_HEADER = Config(
   key="secure_proxy_ssl_header",
   help=_("Support for HTTPS termination at the load-balancer level with SECURE_PROXY_SSL_HEADER."),

+ 16 - 2
desktop/core/src/desktop/middleware.py

@@ -64,6 +64,7 @@ from desktop.lib.metrics import global_registry
 from desktop.lib.view_util import is_ajax
 from desktop.log import get_audit_logger
 from desktop.log.access import access_log, log_page_hit, access_warn
+from desktop.auth.backend import knox_login_headers
 
 from libsaml.conf import CDP_LOGOUT_URL
 
@@ -750,6 +751,7 @@ class SpnegoMiddleware(MiddlewareMixin):
           if user:
             request.user = user
             login(request, user)
+            knox_login_headers(request)
             msg = 'Successful login for user: %s' % request.user.username
           else:
             msg = 'Failed login for user: %s' % request.user.username
@@ -944,15 +946,27 @@ class MultipleProxyMiddleware:
     Rewrites the proxy headers so that only the most
     recent proxy is used.
     """
+    location = -1
+
+    if 'HTTP_X_REAL_IP' in request.META:
+      if 'HTTP_X_FORWARDED_FOR' in request.META and \
+        request.META['HTTP_X_REAL_IP'] in request.META['HTTP_X_FORWARDED_FOR']:
+        location = 0
+        for item in request.META['HTTP_X_FORWARDED_FOR'].split(','):
+          if request.META['HTTP_X_REAL_IP'].strip() != item.strip():
+            location += 1
+          else:
+            request.META['HTTP_X_FORWARDED_FOR'] = item.strip()
+            break;
+
     for field in self.FORWARDED_FOR_FIELDS:
       if field in request.META:
         if ',' in request.META[field]:
           parts = request.META[field].split(',')
-          request.META[field] = parts[-1].strip()
+          request.META[field] = parts[location].strip()
 
     if 'HTTP_X_FORWARDED_FOR' not in request.META and 'REMOTE_ADDR' in request.META:
       request.META['HTTP_X_FORWARDED_FOR'] = request.META['REMOTE_ADDR']
-
     return self.get_response(request)
 
 

+ 31 - 1
desktop/core/src/desktop/middleware_test.py

@@ -22,6 +22,8 @@ import tempfile
 
 from django.conf import settings
 from django.test.client import Client
+from django.test import RequestFactory
+import unittest
 from nose.tools import assert_equal, assert_false, assert_true, assert_not_in
 from nose.plugins.skip import SkipTest
 from django.http import HttpResponse
@@ -29,7 +31,7 @@ from django.core import exceptions
 
 import desktop.conf
 
-from desktop.middleware import CacheControlMiddleware
+from desktop.middleware import CacheControlMiddleware, MultipleProxyMiddleware
 from desktop.conf import AUDIT_EVENT_LOG_DIR, CUSTOM_CACHE_CONTROL
 from desktop.lib.django_test_util import make_logged_in_client
 from desktop.lib.test_utils import add_permission
@@ -233,3 +235,31 @@ def test_cache_control_middleware():
   finally:
     reset()
 
+def get_response(request):
+  return request
+
+class TestMultipleProxyMiddleware(unittest.TestCase):
+
+  def setUp(self):
+    self.factory = RequestFactory()
+    self.middleware = MultipleProxyMiddleware(get_response)
+
+  def test_multiple_proxy_middleware(self):
+    request = self.factory.get('/')
+    request.META['HTTP_X_FORWARDED_FOR'] = '192.0.2.0, 192.0.2.1, 192.0.2.2'
+    request.META['HTTP_X_REAL_IP'] = '192.0.2.1'
+    self.middleware(request)
+    assert_equal(request.META['HTTP_X_FORWARDED_FOR'], '192.0.2.1')
+
+  def test_multiple_proxy_middleware_without_x_real_ip(self):
+    request = self.factory.get('/')
+    request.META['HTTP_X_FORWARDED_FOR'] = '192.0.2.0, 192.0.2.1, 192.0.2.2'
+    self.middleware(request)
+    assert_equal(request.META['HTTP_X_FORWARDED_FOR'], '192.0.2.2')
+
+  def test_multiple_proxy_middleware_without_x_forwarded_for(self):
+    request = self.factory.get('/')
+    request.META['REMOTE_ADDR'] = '192.0.2.0'
+    self.middleware(request)
+    assert_equal(request.META['HTTP_X_FORWARDED_FOR'], '192.0.2.0')
+

+ 8 - 0
desktop/core/src/desktop/views.py

@@ -114,6 +114,14 @@ def samlgroup_check(request):
       LOG.info("User %s found in the required SAML groups %s" % (request.user.username, ",".join(saml_group_found)))
   return True
 
+def saml_login_headers(request):
+  userprofile = get_profile(request.user)
+  try:
+    userprofile.update_data({'X-Forwarded-For': request.META['HTTP_X_FORWARDED_FOR']})
+    userprofile.save()
+  except KeyError as e:
+    LOG.error('X-FORWARDED-FOR header not found: %s' % smart_str(e))
+
 def hue(request):
   current_app, other_apps, apps_list = _get_apps(request.user, '')
   clusters = list(get_clusters(request.user).values())