瀏覽代碼

HUE-2499 [desktop] Allow passwords to be produced by a script

Erick Tryzelaar 11 年之前
父節點
當前提交
6931e2d

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

@@ -25,7 +25,7 @@ from operator import itemgetter
 from django.utils.translation import ugettext as _
 
 from desktop.lib import thrift_util
-from desktop.conf import LDAP_PASSWORD, LDAP_USERNAME
+from desktop.conf import get_ldap_password, LDAP_USERNAME
 from desktop.conf import DEFAULT_USER
 from hadoop import cluster
 
@@ -490,7 +490,10 @@ class HiveServerClient:
     principal = self.query_server['principal']
     impersonation_enabled = False
     ldap_username = None
-    ldap_password = None
+    ldap_password = get_ldap_password()
+
+    if ldap_password is not None: # Pass-through LDAP authentication
+      ldap_username = LDAP_USERNAME.get()
 
     if principal:
       kerberos_principal_short_name = principal.split('/', 1)[0]
@@ -498,7 +501,7 @@ class HiveServerClient:
       kerberos_principal_short_name = None
 
     if self.query_server['server_name'] == 'impala':
-      if LDAP_PASSWORD.get(): # Force LDAP auth if ldap_password is provided
+      if ldap_password: # Force LDAP auth if ldap_password is provided
         use_sasl = True
         mechanism = HiveServerClient.HS2_MECHANISMS['NONE']
       else:
@@ -514,10 +517,6 @@ class HiveServerClient:
       mechanism = HiveServerClient.HS2_MECHANISMS[hive_mechanism]
       impersonation_enabled = hive_site.hiveserver2_impersonation_enabled()
 
-    if LDAP_PASSWORD.get(): # Pass-through LDAP authentication
-      ldap_username = LDAP_USERNAME.get()
-      ldap_password = LDAP_PASSWORD.get()
-
     return use_sasl, mechanism, kerberos_principal_short_name, impersonation_enabled, ldap_username, ldap_password
 
 

+ 1 - 1
apps/useradmin/src/useradmin/ldap_access.py

@@ -45,7 +45,7 @@ def get_connection(ldap_config):
 
   ldap_url = ldap_config.LDAP_URL.get()
   username = ldap_config.BIND_DN.get()
-  password = ldap_config.BIND_PASSWORD.get()
+  password = desktop.conf.get_ldap_password(ldap_config)
   ldap_cert = ldap_config.LDAP_CERT.get()
   search_bind_authentication = ldap_config.SEARCH_BIND_AUTHENTICATION.get()
 

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

@@ -356,7 +356,10 @@ class LdapBackend(object):
       if ldap_config.BIND_DN.get():
         bind_dn = ldap_config.BIND_DN.get()
         setattr(self._backend.settings, 'BIND_DN', bind_dn)
+
         bind_password = ldap_config.BIND_PASSWORD.get()
+        if bind_password is None:
+          password = ldap_config.BIND_PASSWORD_SCRIPT.get()
         setattr(self._backend.settings, 'BIND_PASSWORD', bind_password)
 
       if user_filter is None:

+ 80 - 8
desktop/core/src/desktop/conf.py

@@ -15,10 +15,11 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import logging
 import os
 import socket
 import stat
-import logging
+import subprocess
 
 from django.utils.translation import ugettext_lazy as _
 
@@ -51,6 +52,14 @@ def coerce_port(port):
     return port
 
 
+def coerce_password_from_script(script):
+  password = subprocess.check_output(script, shell=True)
+
+  # whitespace may be significant in the password, but most files have a
+  # trailing newline.
+  return password.strip('\n')
+
+
 HTTP_HOST = Config(
   key="http_host",
   help=_("HTTP host to bind to."),
@@ -91,6 +100,13 @@ LDAP_PASSWORD = Config(
   private=True,
   default=None)
 
+LDAP_PASSWORD_SCRIPT = Config(
+  key="ldap_password_script",
+  help=_("Execute this script to produce the LDAP password. This will be used when `ldap_password` is not set."),
+  private=True,
+  type=coerce_password_from_script,
+  default=None)
+
 LDAP_USERNAME = Config(
   key="ldap_username",
   help=_("LDAP username of the hue user used for LDAP authentications. For example for LDAP Authentication with HiveServer2/Impala."),
@@ -245,6 +261,14 @@ SMTP = ConfigSection(
       default=""
     ),
 
+    PASSWORD_SCRIPT = Config(
+      key="password_script",
+      help=_("Execute this script to produce the SMTP user password. This will be used when the SMTP `password` is not set."),
+      type=coerce_password_from_script,
+      private=True,
+      default=None,
+    ),
+
     USE_TLS = Config(
       key="tls",
       help=_("Whether to use a TLS (secure) connection when talking to the SMTP server."),
@@ -291,6 +315,13 @@ DATABASE = ConfigSection(
       type=str,
       default='',
     ),
+    PASSWORD_SCRIPT=Config(
+      key='password_script',
+      help=_('Execute this script to produce the database password. This will be used when `password` is not set.'),
+      private=True,
+      type=coerce_password_from_script,
+      default='',
+    ),
     HOST=Config(
       key='host',
       help=_('Database host.'),
@@ -553,6 +584,11 @@ LDAP = ConfigSection(
                                default=None,
                                private=True,
                                help=_("The password for the bind user.")),
+          BIND_PASSWORD_SCRIPT=Config("bind_password_script",
+                                    default=None,
+                                    private=True,
+                                    type=coerce_password_from_script,
+                                    help=_("Execute this script to produce the LDAP bind user password. This will be used when `bind_password` is not set.")),
           SEARCH_BIND_AUTHENTICATION=Config("search_bind_authentication",
                                             default=True,
                                             type=coerce_bool,
@@ -794,13 +830,17 @@ def validate_ldap(user, config):
   res = []
 
   if config.SEARCH_BIND_AUTHENTICATION.get():
-    if config.LDAP_URL.get() is not None and bool(config.BIND_DN.get()) != bool(config.BIND_PASSWORD.get()):
-      if config.BIND_DN.get() == None:
-        res.append((LDAP.BIND_DN,
-                  unicode(_("If you set bind_password, then you must set bind_dn."))))
-      else:
-        res.append((LDAP.BIND_PASSWORD,
-                    unicode(_("If you set bind_dn, then you must set bind_password."))))
+    if config.LDAP_URL.get() is not None:
+      bind_dn = config.BIND_DN.get()
+      bind_password = config.BIND_PASSWORD.get() or config.BIND_PASSWORD_SCRIPT.get()
+
+      if bool(bind_dn) != bool(bind_password):
+        if bind_dn == None:
+          res.append((LDAP.BIND_DN,
+                    unicode(_("If you set bind_password, then you must set bind_dn."))))
+        else:
+          res.append((LDAP.BIND_PASSWORD,
+                      unicode(_("If you set bind_dn, then you must set bind_password."))))
   else:
     if config.NT_DOMAIN.get() is not None or \
         config.LDAP_USERNAME_PATTERN.get() is not None:
@@ -907,3 +947,35 @@ def get_redaction_policy():
   """
 
   return LOG_REDACTION_FILE.get()
+
+
+def get_database_password():
+  password = DATABASE.PASSWORD.get()
+  if password is None:
+    password = DATABASE.PASSWORD_SCRIPT.get()
+
+  return password
+
+
+def get_smtp_password():
+  password = SMTP.PASSWORD.get()
+  if password is None:
+    password = SMTP.PASSWORD_SCRIPT.get()
+
+  return password
+
+
+def get_ldap_password():
+  password = LDAP_PASSWORD.get()
+  if password is None:
+    password = LDAP_PASSWORD_SCRIPT.get()
+
+  return password
+
+
+def get_ldap_bind_password(ldap_config):
+  password = ldap_config.BIND_PASSWORD.get()
+  if password is None:
+    password = ldap_config.BIND_PASSWORD_SCRIPT.get()
+
+  return password

+ 2 - 2
desktop/core/src/desktop/settings.py

@@ -289,7 +289,7 @@ else:
     "ENGINE" : desktop.conf.DATABASE.ENGINE.get(),
     "NAME" : desktop.conf.DATABASE.NAME.get(),
     "USER" : desktop.conf.DATABASE.USER.get(),
-    "PASSWORD" : desktop.conf.DATABASE.PASSWORD.get(),
+    "PASSWORD" : desktop.conf.get_database_password(),
     "HOST" : desktop.conf.DATABASE.HOST.get(),
     "PORT" : str(desktop.conf.DATABASE.PORT.get()),
     "OPTIONS": force_dict_to_strings(desktop.conf.DATABASE.OPTIONS.get()),
@@ -331,7 +331,7 @@ if desktop.conf.DEMO_ENABLED.get():
 EMAIL_HOST = desktop.conf.SMTP.HOST.get()
 EMAIL_PORT = desktop.conf.SMTP.PORT.get()
 EMAIL_HOST_USER = desktop.conf.SMTP.USER.get()
-EMAIL_HOST_PASSWORD = desktop.conf.SMTP.PASSWORD.get()
+EMAIL_HOST_PASSWORD = desktop.conf.get_smtp_password()
 EMAIL_USE_TLS = desktop.conf.SMTP.USE_TLS.get()
 DEFAULT_FROM_EMAIL = desktop.conf.SMTP.DEFAULT_FROM.get()
 

+ 98 - 4
desktop/core/src/desktop/tests.py

@@ -16,14 +16,16 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import desktop
-import desktop.urls
-import desktop.conf
-import logging
 import json
+import logging
 import os
+import sys
+import tempfile
 import time
 
+import desktop
+import desktop.conf
+import desktop.urls
 import desktop.views as views
 import proxy.conf
 
@@ -683,3 +685,95 @@ class TestStrictRedirection():
     if expected_status_code == 403:
         error_msg = 'Redirect to ' + redirection_url + ' is not allowed.'
         assert_true(error_msg in response.content, response.content)
+
+
+class BaseTestPasswordConfig(object):
+
+  SCRIPT = '%s -c "print \'\\n password from script \\n\'"' % sys.executable
+
+  def get_config_password(self):
+    raise NotImplementedError
+
+  def get_config_password_script(self):
+    raise NotImplementedError
+
+  def get_password(self):
+    raise NotImplementedError
+
+  def test_read_password_from_script(self):
+    resets = [
+      self.get_config_password().set_for_testing(None),
+      self.get_config_password_script().set_for_testing(self.SCRIPT)
+    ]
+
+    try:
+      assert_equal(self.get_password(), ' password from script ')
+    finally:
+      for reset in resets:
+        reset()
+
+
+  def test_config_password_overrides_script_password(self):
+
+    resets = [
+      self.get_config_password().set_for_testing(' password from config '),
+      self.get_config_password_script().set_for_testing(self.SCRIPT),
+    ]
+
+    try:
+      assert_equal(self.get_password(), ' password from config ')
+    finally:
+      for reset in resets:
+        reset()
+
+
+class TestDatabasePasswordConfig(BaseTestPasswordConfig):
+
+  def get_config_password(self):
+    return desktop.conf.DATABASE.PASSWORD
+
+  def get_config_password_script(self):
+    return desktop.conf.DATABASE.PASSWORD_SCRIPT
+
+  def get_password(self):
+    return desktop.conf.get_database_password()
+
+
+class TestLDAPPasswordConfig(BaseTestPasswordConfig):
+
+  def get_config_password(self):
+    return desktop.conf.LDAP_PASSWORD
+
+  def get_config_password_script(self):
+    return desktop.conf.LDAP_PASSWORD_SCRIPT
+
+  def get_password(self):
+    return desktop.conf.get_ldap_password()
+
+class TestLDAPBindPasswordConfig(BaseTestPasswordConfig):
+
+  def setup(self):
+    self.finish = desktop.conf.LDAP.LDAP_SERVERS.set_for_testing({'test': {}})
+
+  def teardown(self):
+    self.finish()
+
+  def get_config_password(self):
+    return desktop.conf.LDAP.LDAP_SERVERS['test'].BIND_PASSWORD
+
+  def get_config_password_script(self):
+    return desktop.conf.LDAP.LDAP_SERVERS['test'].BIND_PASSWORD_SCRIPT
+
+  def get_password(self):
+    return desktop.conf.get_ldap_bind_password(desktop.conf.LDAP.LDAP_SERVERS['test'])
+
+class TestSMTPPasswordConfig(BaseTestPasswordConfig):
+
+  def get_config_password(self):
+    return desktop.conf.SMTP.PASSWORD
+
+  def get_config_password_script(self):
+    return desktop.conf.SMTP.PASSWORD_SCRIPT
+
+  def get_password(self):
+    return desktop.conf.get_smtp_password()

+ 19 - 1
desktop/libs/librdbms/src/librdbms/conf.py

@@ -18,7 +18,7 @@
 from django.utils.translation import ugettext_lazy as _t, ugettext as _
 from desktop.lib.conf import Config, UnspecifiedConfigSection,\
                              ConfigSection, coerce_json_dict
-from desktop.conf import coerce_database
+from desktop.conf import coerce_database, coerce_password_from_script
 
 
 DATABASES = UnspecifiedConfigSection(
@@ -57,6 +57,12 @@ DATABASES = UnspecifiedConfigSection(
         type=str,
         default='',
       ),
+      PASSWORD_SCRIPT=Config(
+        key='password_script',
+        help=_t('Execute this script to produce the database password. This will be used when `password` is not set.'),
+        type=coerce_password_from_script,
+        default=None,
+      ),
       HOST=Config(
         key='host',
         help=_t('Database host.'),
@@ -98,3 +104,15 @@ def config_validator(user):
 
 def get_server_choices():
   return [(alias, DATABASES[alias].NICE_NAME.get() or alias) for alias in DATABASES]
+
+
+def get_database_password(name):
+  """
+  Return the configured database password.
+  """
+
+  password = DATABASES[name].PASSWORD.get()
+  if password is None:
+    password = DATABASES[name].PASSWORD_SCRIPT.get()
+
+  return password

+ 2 - 2
desktop/libs/librdbms/src/librdbms/server/dbms.py

@@ -19,7 +19,7 @@ import logging
 
 from desktop.lib.python_util import force_dict_to_strings
 
-from librdbms.conf import DATABASES
+from librdbms.conf import DATABASES, get_database_password
 
 
 LOG = logging.getLogger(__name__)
@@ -65,7 +65,7 @@ def get_query_server_config(server=None):
       'server_host': DATABASES[name].HOST.get(),
       'server_port': DATABASES[name].PORT.get(),
       'username': DATABASES[name].USER.get(),
-      'password': DATABASES[name].PASSWORD.get(),
+      'password': get_database_password(name),
       'options': force_dict_to_strings(DATABASES[name].OPTIONS.get()),
       'alias': name
     }

+ 21 - 0
desktop/libs/librdbms/src/librdbms/tests.py

@@ -14,3 +14,24 @@
 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 # See the License for the specific language governing permissions and
 # limitations under the License.
+
+import desktop.tests
+import librdbms.conf
+
+
+class TestDatabasePasswordConfig(desktop.tests.BaseTestPasswordConfig):
+
+  def setup(self):
+    self.finish = librdbms.conf.DATABASES.set_for_testing({'test': {}})
+
+  def teardown(self):
+    self.finish()
+
+  def get_config_password(self):
+    return librdbms.conf.DATABASES['test'].PASSWORD
+
+  def get_config_password_file(self):
+    return librdbms.conf.DATABASES['test'].PASSWORD_FILE
+
+  def get_password(self):
+    return librdbms.conf.get_database_password('test')