Browse Source

HUE-8750 [core] Integrate with Apache Knox

Prakash Ranade 6 years ago
parent
commit
ecff05e44d

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

@@ -336,6 +336,7 @@
     # - desktop.auth.backend.LdapBackend
     # - desktop.auth.backend.PamBackend
     # - desktop.auth.backend.SpnegoDjangoBackend
+    # - desktop.auth.backend.KnoxSpnegoDjangoBackend
     # - desktop.auth.backend.RemoteUserDjangoBackend
     # - libsaml.backend.SAML2Backend
     # - libopenid.backend.OpenIDBackend
@@ -683,6 +684,15 @@
     ## default_from_email=hue@localhost
 
 
+  # Configuration options for KNOX integration for secured CDPD cluster
+  # ------------------------------------------------------------------------
+  [[knox]]
+
+    # This is a list of hosts that knox proxy requests can come from
+    ## knox_proxyhosts="server1.domain.com,server2.domain.com"
+    # Kerberos principal name which is allowed to impersonate others
+    ## knox_principal=knox
+
   # Configuration options for Kerberos integration for secured Hadoop clusters
   # ------------------------------------------------------------------------
   [[kerberos]]

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

@@ -340,6 +340,7 @@
     # - desktop.auth.backend.LdapBackend
     # - desktop.auth.backend.PamBackend
     # - desktop.auth.backend.SpnegoDjangoBackend
+    # - desktop.auth.backend.KnoxSpnegoDjangoBackend
     # - desktop.auth.backend.RemoteUserDjangoBackend
     # - libsaml.backend.SAML2Backend
     # - libopenid.backend.OpenIDBackend
@@ -684,6 +685,15 @@
     # Default email address to use for various automated notification from Hue
     ## default_from_email=hue@localhost
 
+  # Configuration options for KNOX integration for secured CDPD cluster
+  # ------------------------------------------------------------------------
+  [[knox]]
+
+    # This is a list of hosts that knox proxy requests can come from
+    ## knox_proxyhosts="server1.domain.com,server2.domain.com"
+    # Kerberos principal name which is allowed to impersonate others
+    ## knox_principal=knox
+
 
   # Configuration options for Kerberos integration for secured Hadoop clusters
   # ------------------------------------------------------------------------

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

@@ -602,6 +602,50 @@ class SpnegoDjangoBackend(django.contrib.auth.backends.ModelBackend):
     user = rewrite_user(user)
     return user
 
+class KnoxSpnegoDjangoBackend(django.contrib.auth.backends.ModelBackend):
+  """
+  Knox Trusted proxy passes GET parameter doAs
+  """
+
+  @metrics.spnego_authentication_time
+  def authenticate(self, request=None, username=None):
+    username = self.clean_username(username, request)
+    username = force_username_case(username)
+    is_super = False
+    if User.objects.count() == 0:
+      is_super = True
+
+    try:
+      if desktop.conf.AUTH.IGNORE_USERNAME_CASE.get():
+        user = User.objects.get(username__iexact=username)
+      else:
+        user = User.objects.get(username=username)
+    except User.DoesNotExist:
+      user = find_or_create_user(username, None)
+      if user is not None and user.is_active:
+        profile = get_profile(user)
+        profile.creation_method = UserProfile.CreationMethod.EXTERNAL.name
+        profile.save()
+        user.is_superuser = is_super
+
+        ensure_has_a_group(user)
+
+        user.save()
+
+    if user is not None:
+      user = rewrite_user(user)
+
+    return user
+
+  def clean_username(self, username, request):
+    # Grab doAs parameter here
+    doas_user = request.GET.get("doAs", "")
+    return doas_user
+
+  def get_user(self, user_id):
+    user = super(KnoxSpnegoDjangoBackend, self).get_user(user_id)
+    user = rewrite_user(user)
+    return user
 
 
 class RemoteUserDjangoBackend(django.contrib.auth.backends.RemoteUserBackend):

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

@@ -829,6 +829,24 @@ SESSION = ConfigSection(
   )
 )
 
+KNOX = ConfigSection(
+  key="knox",
+  help=_("""Configuration options for specifying Hue's KNOX integration for secured Hadoop clusters."""),
+  members=dict(
+    KNOX_PRINCIPAL=Config(
+      key='knox_principal',
+      help=_("Kerberos principal name for Hue. Typically 'knox/hostname.foo.com'."),
+      type=str,
+      default="knox/%s" % socket.getfqdn()),
+    KNOX_PROXYHOSTS = Config(
+      key='knox_proxyhosts',
+      default="%s" % socket.getfqdn(),
+      type=str,
+      help=_('Comma separated list of strings representing the host names that the Hue server can trust as knox hosts.')
+    ),
+  )
+)
+
 KERBEROS = ConfigSection(
   key="kerberos",
   help=_("""Configuration options for specifying Hue's Kerberos integration for

+ 31 - 5
desktop/core/src/desktop/middleware.py

@@ -51,7 +51,7 @@ from desktop.lib.django_util import render, render_json
 from desktop.lib.exceptions import StructuredException
 from desktop.lib.exceptions_renderable import PopupException
 from desktop.log import get_audit_logger
-from desktop.log.access import access_log, log_page_hit
+from desktop.log.access import access_log, log_page_hit, access_warn
 from desktop import appmanager
 from desktop import metrics
 from hadoop import cluster
@@ -332,7 +332,9 @@ class LoginAndPermissionMiddleware(object):
 
     logging.info("Redirecting to login page: %s", request.get_full_path())
     access_log(request, 'login redirection', level=access_log_level)
-    no_idle_backends = ("libsaml.backend.SAML2Backend", "desktop.auth.backend.SpnegoDjangoBackend")
+    no_idle_backends = ("libsaml.backend.SAML2Backend",
+                        "desktop.auth.backend.SpnegoDjangoBackend",
+                        "desktop.auth.backend.KnoxSpnegoDjangoBackend")
     if request.ajax and all(no_idle_backend not in desktop.conf.AUTH.BACKEND.get() for no_idle_backend in no_idle_backends):
       # Send back a magic header which causes Hue.Request to interpose itself
       # in the ajax request and make the user login before resubmitting the
@@ -532,7 +534,9 @@ class SpnegoMiddleware(object):
   """
 
   def __init__(self):
-    if not 'desktop.auth.backend.SpnegoDjangoBackend' in desktop.conf.AUTH.BACKEND.get():
+    if not set(desktop.conf.AUTH.BACKEND.get()).intersection(
+            set(['desktop.auth.backend.SpnegoDjangoBackend', 'desktop.auth.backend.KnoxSpnegoDjangoBackend'])
+            ):
       LOG.info('Unloading SpnegoMiddleware')
       raise exceptions.MiddlewareNotUsed
 
@@ -600,11 +604,28 @@ class SpnegoMiddleware(object):
           username = kerberos.authGSSServerUserName(context)
           kerberos.authGSSServerClean(context)
 
+          # In Trusted knox proxy, Hue must expect following:
+          #   Trusted knox user: KNOX_PRINCIPAL
+          #   Trusted knox proxy host: KNOX_PROXYHOSTS
+          if 'desktop.auth.backend.KnoxSpnegoDjangoBackend' in \
+                desktop.conf.AUTH.BACKEND.get():
+            knox_verification = False
+            if desktop.conf.KNOX.KNOX_PRINCIPAL.get() in username:
+              # This may contain chain of reverse proxies, e.g. knox proxy, hue load balancer
+              req_hosts = self.clean_host(request.META['HTTP_X_FORWARDED_HOST'])
+              knox_proxy = self.clean_host(desktop.conf.KNOX.KNOX_PROXYHOSTS.get())
+              if req_hosts.intersection(knox_proxy):
+                knox_verification = True
+            # If knox authentication failed then generate 401 (Unauthorized error)
+            if not knox_verification:
+              request.META['Return-401'] = ''
+              return
+
           if request.user.is_authenticated():
             if request.user.username == self.clean_username(username, request):
               return
 
-          user = authenticate(username=username)
+          user = authenticate(username=username, request=request)
           if user:
             request.user = user
             login(request, user)
@@ -629,6 +650,11 @@ class SpnegoMiddleware(object):
         request.META['Return-401'] = ''
       return
 
+  def clean_host(self, pattern):
+    if pattern:
+      return set([hostport.split(':')[0] for hostport in pattern.split(',')])
+    return set([])
+
   def clean_username(self, username, request):
     """
     Allows the backend to clean the username, if the backend defines a
@@ -637,7 +663,7 @@ class SpnegoMiddleware(object):
     backend_str = request.session[BACKEND_SESSION_KEY]
     backend = load_backend(backend_str)
     try:
-      username = backend.clean_username(username)
+      username = backend.clean_username(username, request)
     except AttributeError:
       pass
     return username