Author: tmckay
Date: 2012-05-09 17:06:39 +0000 (Wed, 09 May 2012)
New Revision: 5368
Modified:
trunk/cumin/bin/cumin-web
trunk/cumin/etc/cumin.conf
trunk/cumin/python/cumin/authenticator.py
trunk/cumin/python/cumin/config.py
trunk/cumin/python/cumin/main.py
Log:
Added SSL for communication with LDAP. If the certificate authority config
params are set for Cumin, then Cumin will communicate using SSL with
servers that have a scheme of "ldaps://" and will use the StartTLS mechanism
for servers that have a schme of "ldap://" when doing binds.
BZ737979 (mod)
Modified: trunk/cumin/bin/cumin-web
===================================================================
--- trunk/cumin/bin/cumin-web 2012-05-09 15:12:54 UTC (rev 5367)
+++ trunk/cumin/bin/cumin-web 2012-05-09 17:06:39 UTC (rev 5368)
@@ -50,6 +50,7 @@
# Default will be off.
cumin.do_authorize = values.authorize
+ # This is the path to the xml file defining authorization.
# Allow this to be disabled via "None"
if values.auth_path == "None":
cumin.access_path = None
@@ -61,6 +62,11 @@
else:
cumin.access_path = values.auth_path
+def set_ldap_configs(cumin, values):
+ cumin.ldap_timeout = values.ldap_timeout
+ cumin.ldap_tls_cacertdir = values.ldap_tls_cacertdir
+ cumin.ldap_tls_cacertfile = values.ldap_tls_cacertfile
+
def adjust_return(passed_init, ret):
# Shift non-zer0 return codes left 1 bit
# and OR in whether or not init passed
@@ -156,6 +162,7 @@
set_aviary_configs(cumin, values)
set_wallaby_configs(cumin, values, broker_uris)
+ set_ldap_configs(cumin, values)
# Not used right now
#cumin.auth_create_ondemand = values.auth_create_ondemand
Modified: trunk/cumin/etc/cumin.conf
===================================================================
--- trunk/cumin/etc/cumin.conf 2012-05-09 15:12:54 UTC (rev 5367)
+++ trunk/cumin/etc/cumin.conf 2012-05-09 17:06:39 UTC (rev 5368)
@@ -110,6 +110,10 @@
# max-qmf-table-sort: 1000
# persona: grid
# authorize: False
+# auth: internal
+# ldap_tls_cacertfile:
+# ldap_tls_cacertdir:
+# ldap_timeout: 30
#notification-timeout: 180
# Number of seconds that a message in the yellow notification banner
@@ -289,6 +293,41 @@
## role will not see the 'Administrator' tab.
## The default role for a new user is 'user'.
+## auth: internal
+## Allows specification of LDAP servers for external authentication of
+## users during login. A server must support simple bind for authentication.
+## This is a semicolon separated list of URLs of the following form:
+##
+## ldap://host:port/dn??scope?filter
+##
+## Scope defaults to 'sub', filter defaults to 'uid=usnername'.
+## If a filter is specified Cumin will replace occurrences of %%s
+## with username. Example:
+##
ldap://alpha.exmple.com/ou=users,dc=example,dc=com;
+##
ldap://beta.example.com/ou=people,dc=example,dc=com??one?mail=%%s
+
+## ldap_tls_cacertfile:
+## Setting this option turns on SSL communication to all LDAP servers.
+## Specifies a file containing certificates for all the Certificate
+## Authorities that Cumin will recognize. An LDAP server certificate
+## must be issued by a recognized Certificate Authority or Cumin will
+## refuse to communicate with the server.
+## If this option is set, Cumin will look at the specified file before
+## looking in the directory specified by ldap_tls_cacertdir.
+
+## ldap_tls_cacertdir:
+## Setting this option turns on SSL communication to all LDAP servers.
+## Specifies a directory containing certificates for all the Certificate
+## Authorities that Cumin will recognize. Certificates may be in individual
+## files or in a certificate database created with certutil.
+## An LDAP server certificate must be issued by a recognized Certificate
+## Authority or Cumin will refuse to communicate with the server.
+## If ldap_tls_cacertfile is set, Cumin will look in that file before
+## looking in this directory.
+
+## ldap_timeout: 30
+## Timeout in seconds for connections to an LDAP server
+
## [data]
## expire-enabled: True
Modified: trunk/cumin/python/cumin/authenticator.py
===================================================================
--- trunk/cumin/python/cumin/authenticator.py 2012-05-09 15:12:54 UTC (rev 5367)
+++ trunk/cumin/python/cumin/authenticator.py 2012-05-09 17:06:39 UTC (rev 5368)
@@ -26,29 +26,104 @@
syslog.syslog(msg)
class CuminAuthenticatorLDAP(object):
- def __init__(self, url):
+ def __init__(self, url, app):
self.valid = False
log.info("Initializing %s", self)
- myurl = url.split("=",1)[1]
- try:
- self.url = ldapurl.LDAPUrl(myurl,
- scope=ldapurl.LDAP_SCOPE_SUBTREE,
- filterstr="uid=%s")
- log.info("Authenticator: parsed LDAP url successfully.")
- self.valid = True
- except Exception,e:
- log.error("Authenticator: cannot parse LDAP url %s", e)
-
+ scheme, myurl = url.split("=",1)
+ err_msg = ""
+
+ self.tls_cacertdir = app.ldap_tls_cacertdir
+ self.tls_cacertfile = app.ldap_tls_cacertfile
+ self.ldap_timeout = app.ldap_timeout
+ has_cert = self.tls_cacertdir or self.tls_cacertfile
+
+ if has_cert and not ldap.TLS_AVAIL:
+ err_msg = "Authenticator: SSL is not supported by this python-ldap
module."
+
+ elif not has_cert and scheme == "ldaps":
+ err_msg = "Authenticator: scheme is ldaps but no tls_cacertdir
set."
+
+ else:
+ if self.tls_cacertdir:
+ if not os.path.isdir(self.tls_cacertdir):
+ err_msg = "Authenticator: '%s' is not a valid
directory." \
+ % self.tls_cacertdir
+ else:
+ try:
+ os.listdir(self.tls_cacertdir)
+ except Exception, e:
+ err_msg = "Authenticator, exception reading " \
+ "tls_cacertdir, %s" % e
+
+ if not err_msg and self.tls_cacertfile:
+ if not os.path.isfile(self.tls_cacertfile):
+ err_msg = "Authenticator: '%s' is not a valid
file." \
+ % self.tls_cacertfile
+ else:
+ try:
+ open(self.tls_cacertfile, "r")
+ except Exception, e:
+ err_msg = "Authenticator, exception reading " \
+ "tls_cacertfile, %s" % e
+ if not err_msg:
+ # If the scheme is just "ldap" but cert dir is set then we will
use
+ # the StartTLS mechanism for password binds
+ self.start_tls = has_cert and scheme == "ldap"
+ if scheme == "ldap":
+ log.info("Authenticator: using StartTLS for ldap: %s" % \
+ self.start_tls)
+ try:
+ self.url = ldapurl.LDAPUrl(myurl,
+ scope=ldapurl.LDAP_SCOPE_SUBTREE,
+ filterstr="uid=%s")
+ log.info("Authenticator: parsed LDAP url successfully, " \
+ "scheme is %s" % scheme)
+ self.valid = True
+ except Exception,e:
+ err_msg = "Authenticator: cannot parse LDAP url %s" % e
+
+ if err_msg:
+ log.error(err_msg)
+
+ def _set_ldap_options(self):
+ if self.tls_cacertdir:
+ ldap.set_option(ldap.OPT_X_TLS_CACERTDIR, self.tls_cacertdir)
+
+ if self.tls_cacertfile:
+ ldap.set_option(ldap.OPT_X_TLS_CACERTFILE, self.tls_cacertfile)
+
+ if self.tls_cacertfile or self.tls_cacertdir:
+ # Always demand a certificate if the cacert is set
+ ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_DEMAND)
+
+ if self.ldap_timeout is not None:
+ ldap.set_option(ldap.OPT_TIMEOUT, self.ldap_timeout)
+
+ def _start_tls(self, conn):
+ if self.start_tls and not conn.start_tls_called:
+ conn.start_tls_called = True
+ log.debug("Authenticator: calling conn.start_tls_s() on ldap
connection")
+ try:
+ conn.start_tls_s()
+ except Exception, e:
+ log.error("Authenticator: StartTLS failed, %s" % e)
+ return False
+ return True
+
def __prep_con(self):
try:
+ self._set_ldap_options()
conn = ldap.initialize(self.url.urlscheme + "://"
+self.url.hostport)
+ conn.start_tls_called = False
except Exception,e:
log.error("Authenticator: cannot contact ldap server %s",e)
return False
if self.url.who and self.url.cred:
+ if not self._start_tls(conn):
+ return False
try:
- conn.simple_bind_s(self.url.who,self.url.cred)
+ conn.simple_bind_s(self.url.who, self.url.cred)
except:
log.error("Authenticator: cannot authenticate as service %s",
self.url.who)
@@ -84,13 +159,15 @@
log.info("Authenticator: update password, "\
"query returned no results in %s",
self.__class__.__name__)
else:
- dn,ent = res[0]
- conn.simple_bind_s(dn,oldpass)
- conn.passwd_s(dn,oldpass,newpass)
- log.info("Authenticator: update password succeeded in %s",
- self.__class__.__name__)
- _syslog.log("cumin: updated password via LDAP for user %s" % dn)
- res = True
+ res = False
+ if self._start_tls(conn):
+ dn,ent = res[0]
+ conn.simple_bind_s(dn,oldpass)
+ conn.passwd_s(dn,oldpass,newpass)
+ log.info("Authenticator: update password succeeded in %s",
+ self.__class__.__name__)
+ _syslog.log("cumin: updated password via LDAP for user %s" %
dn)
+ res = True
return res
def batch_import(self):
@@ -137,18 +214,19 @@
"ldap users")
return False
- for dn,ent in res:
- try:
- conn.simple_bind_s(dn,password)
- _syslog.log("cumin: authenticated user %s via LDAP" % dn)
- return True
- except Exception, e:
- log.info("Authenticator: LDAP authentication "\
- "returned exception %s" % e)
+ if self._start_tls(conn):
+ for dn,ent in res:
+ try:
+ conn.simple_bind_s(dn,password)
+ _syslog.log("cumin: authenticated user %s via LDAP" %
dn)
+ return True
+ except Exception, e:
+ log.info("Authenticator: LDAP authentication "\
+ "returned exception %s" % e)
return False
class CuminAuthenticatorScript(object):
- def __init__(self, commandline):
+ def __init__(self, commandline, *args):
self.valid = False
self.params = {}
self.cap = []
@@ -299,6 +377,7 @@
if have_ldap:
authen_map = {'ldap': CuminAuthenticatorLDAP,
+ 'ldaps': CuminAuthenticatorLDAP,
'script': CuminAuthenticatorScript}
else:
authen_map = {'script': CuminAuthenticatorScript}
@@ -319,6 +398,9 @@
if authmech.startswith("ldap://"):
mechname = "ldap"
authmech = "ldap=" + authmech
+ elif authmech.startswith("ldaps://"):
+ mechname = "ldaps"
+ authmech = "ldaps=" + authmech
else:
mechname = authmech.split('=',1)[0]
classname = authen_map[mechname]
@@ -327,10 +409,11 @@
mechname)
continue
- c = classname(authmech)
+ c = classname(authmech, self.app)
if c.valid:
- log.info("Authenticator: adding auth mechanism %s",
mechname)
self.authenticators.append(c)
+ log.info("Authenticator: adding auth mechanism %s %s" \
+ % (mechname, c.valid and "succeeded" or
"failed"))
def batch_import(self, backend, output_on=False):
numusers = 0
Modified: trunk/cumin/python/cumin/config.py
===================================================================
--- trunk/cumin/python/cumin/config.py 2012-05-09 15:12:54 UTC (rev 5367)
+++ trunk/cumin/python/cumin/config.py 2012-05-09 17:06:39 UTC (rev 5368)
@@ -115,6 +115,15 @@
param = ConfigParameter(self, "auth", str)
param.default = 'internal'
+ param = ConfigParameter(self, "ldap_tls_cacertfile", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "ldap_tls_cacertdir", str)
+ param.default = ""
+
+ param = ConfigParameter(self, "ldap_timeout", int)
+ param.default = 30
+
self.log_file = ConfigParameter(self, "log-file", str)
param = ConfigParameter(self, "log-level", str)
Modified: trunk/cumin/python/cumin/main.py
===================================================================
--- trunk/cumin/python/cumin/main.py 2012-05-09 15:12:54 UTC (rev 5367)
+++ trunk/cumin/python/cumin/main.py 2012-05-09 17:06:39 UTC (rev 5368)
@@ -40,6 +40,11 @@
self.home = home
self.authmech = authmech
+ # Settings for LDAP authentication
+ self.ldap_timeout = None
+ self.ldap_tls_cacertdir = ""
+ self.ldap_tls_cacertfile = ""
+
model_dir = [os.path.join(self.home, x) for x in ("model/admin",
"model", "model/plumage")]
self.model = CuminModel(self, model_dir)
@@ -48,7 +53,6 @@
self.database = CuminDatabase(self, database_dsn)
self.server = CuminServer(self, host, port)
self.admin = CuminAdmin(self)
- self.authenticator = CuminAuthenticator(self)
self.add_resource_dir(os.path.join(self.home, "resources-wooly"))
self.add_resource_dir(os.path.join(self.home, "resources"))
@@ -162,16 +166,12 @@
self.model.init()
self.database.init(schema_version_check)
- def db_init(self, schema_version_check=True):
- self.model.init()
- self.database.init(schema_version_check)
-
def init(self, schema_version_check=True):
log.info("Initializing %s", self)
# Do this initialization as late as possible so that
- # the application can set a value for self.access_path.
- # Alternatively, it can be an argument to the constructor.
+ # the application can set config values.
+ self.authenticator = CuminAuthenticator(self)
self.authorizator = CuminAuthorizator(self, self.access_path, self.do_authorize)
try:
self.authorizator.set_persona(self.persona)