From cca9d2ab2dced4eb4769c558c668467dfcbd262d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lauri=20V=C3=B5sandi?= Date: Wed, 25 Jan 2017 09:43:19 +0000 Subject: [PATCH] Refactor LDAP authentication * ldap uri can be specified in /etc/certidude/server.conf now * /etc/ldap/ldap.conf is ignored --- certidude/api/__init__.py | 1 - certidude/auth.py | 49 ++++++++++++++--------- certidude/config.py | 19 ++------- certidude/templates/certidude-server.conf | 5 ++- certidude/user.py | 16 ++++---- 5 files changed, 44 insertions(+), 46 deletions(-) diff --git a/certidude/api/__init__.py b/certidude/api/__init__.py index 531aa27..8cb51dd 100644 --- a/certidude/api/__init__.py +++ b/certidude/api/__init__.py @@ -44,7 +44,6 @@ class SessionResource(object): @login_required @event_source def on_get(self, req, resp): - return dict( user = dict( name=req.context.get("user").name, diff --git a/certidude/auth.py b/certidude/auth.py index 872cce3..5a2c60b 100644 --- a/certidude/auth.py +++ b/certidude/auth.py @@ -122,9 +122,9 @@ def authenticate(optional=False): import ldap if not req.auth: - resp.append_header("WWW-Authenticate", "Basic") - raise falcon.HTTPUnauthorized("Forbidden", - "Please authenticate with %s domain account or supply UPN" % const.DOMAIN) + raise falcon.HTTPUnauthorized("Unauthorized", + "No authentication header provided", + ("Basic",)) if not req.auth.startswith("Basic "): raise falcon.HTTPForbidden("Forbidden", "Bad header: %s" % req.auth) @@ -133,26 +133,35 @@ def authenticate(optional=False): basic, token = req.auth.split(" ", 1) user, passwd = b64decode(token).split(":", 1) - for server in config.LDAP_SERVERS: - click.echo("Connecting to %s as %s" % (server, user)) - conn = ldap.initialize(server) - conn.set_option(ldap.OPT_REFERRALS, 0) - try: - conn.simple_bind_s(user if "@" in user else "%s@%s" % (user, const.DOMAIN), passwd) - except ldap.LDAPError, e: - resp.append_header("WWW-Authenticate", "Basic") - logger.critical(u"LDAP bind authentication failed for user %s from %s", - repr(user), req.context.get("remote_addr")) - raise falcon.HTTPUnauthorized("Forbidden", - "Please authenticate with %s domain account or supply UPN" % const.DOMAIN) + click.echo("Connecting to %s as %s" % (config.LDAP_AUTHENTICATION_URI, user)) + conn = ldap.initialize(config.LDAP_AUTHENTICATION_URI) + conn.set_option(ldap.OPT_REFERRALS, 0) - req.context["ldap_conn"] = conn - break - else: - raise ValueError("No LDAP servers!") + if "@" not in user: + user = "%s@%s" % (user, const.DOMAIN) + logger.debug("Expanded username to %s", user) + try: + conn.simple_bind_s(user, passwd) + except ldap.STRONG_AUTH_REQUIRED: + logger.critical("LDAP server demands encryption, use ldaps:// instead of ldaps://") + raise + except ldap.SERVER_DOWN: + logger.critical("Failed to connect LDAP server at %s, are you sure LDAP server's CA certificate has been copied to this machine?", + config.LDAP_AUTHENTICATION_URI) + raise + except ldap.INVALID_CREDENTIALS: + logger.critical(u"LDAP bind authentication failed for user %s from %s", + repr(user), req.context.get("remote_addr")) + raise falcon.HTTPUnauthorized("Forbidden", + "Please authenticate with %s domain account or supply UPN" % const.DOMAIN, + ("Basic",)) + + req.context["ldap_conn"] = conn req.context["user"] = User.objects.get(user) - return func(resource, req, resp, *args, **kwargs) + retval = func(resource, req, resp, *args, **kwargs) + conn.unbind_s() + return retval def pam_authenticate(resource, req, resp, *args, **kwargs): diff --git a/certidude/config.py b/certidude/config.py index adfd4a8..7fed3d6 100644 --- a/certidude/config.py +++ b/certidude/config.py @@ -18,8 +18,10 @@ AUTHENTICATION_BACKENDS = set([j for j in AUTHORIZATION_BACKEND = cp.get("authorization", "backend") # whitelist, ldap, posix ACCOUNTS_BACKEND = cp.get("accounts", "backend") # posix, ldap -if ACCOUNTS_BACKEND == "ldap": - LDAP_GSSAPI_CRED_CACHE = cp.get("accounts", "ldap gssapi credential cache") +LDAP_AUTHENTICATION_URI = cp.get("authentication", "ldap uri") +LDAP_GSSAPI_CRED_CACHE = cp.get("accounts", "ldap gssapi credential cache") +LDAP_ACCOUNTS_URI = cp.get("accounts", "ldap uri") +LDAP_BASE = cp.get("accounts", "ldap base") USER_SUBNETS = set([ipaddress.ip_network(j) for j in cp.get("authorization", "user subnets").split(" ") if j]) @@ -78,17 +80,4 @@ elif "ldap" == AUTHORIZATION_BACKEND: else: raise NotImplementedError("Unknown authorization backend '%s'" % AUTHORIZATION_BACKEND) -for line in open("/etc/ldap/ldap.conf"): - line = line.strip().lower() - if "#" in line: - line, _ = line.split("#", 1) - if not " " in line: - continue - key, value = line.split(" ", 1) - if key == "uri": - LDAP_SERVERS = set([j for j in value.split(" ") if j]) - click.echo("LDAP servers: %s" % " ".join(LDAP_SERVERS)) - elif key == "base": - LDAP_BASE = value - # TODO: Check if we don't have base or servers diff --git a/certidude/templates/certidude-server.conf b/certidude/templates/certidude-server.conf index b5da14a..7866be0 100644 --- a/certidude/templates/certidude-server.conf +++ b/certidude/templates/certidude-server.conf @@ -9,11 +9,12 @@ backends = pam ;backends = ldap ;backends = kerberos ldap ;backends = kerberos pam +ldap uri = ldaps://dc1.example.com [accounts] # The accounts backend specifies how the user's given name, surname and e-mail # address are looked up. In case of 'posix' basically 'getent passwd' is performed, -# in case of 'ldap' a search is performed on LDAP server specified in /etc/ldap/ldap.conf +# in case of 'ldap' a search is performed on LDAP server specified by ldap uri # with Kerberos credential cache initialized at path specified by environment variable KRB5CCNAME # If certidude setup authority was performed correctly the credential cache should be # updated automatically by /etc/cron.hourly/certidude @@ -21,6 +22,8 @@ backends = pam backend = posix ;backend = ldap ldap gssapi credential cache = /run/certidude/krb5cc +ldap uri = ldap://dc1.example.com +ldap base = {% if base %}{{ base }}{% else %}dc=example,dc=com{% endif %} [authorization] # The authorization backend specifies how the users are authorized. diff --git a/certidude/user.py b/certidude/user.py index d31198b..23996ca 100644 --- a/certidude/user.py +++ b/certidude/user.py @@ -70,17 +70,15 @@ class DirectoryConnection(object): raise ValueError("Ticket cache at %s not initialized, unable to " "authenticate with computer account against LDAP server!" % config.LDAP_GSSAPI_CRED_CACHE) os.environ["KRB5CCNAME"] = config.LDAP_GSSAPI_CRED_CACHE - for server in config.LDAP_SERVERS: - self.conn = ldap.initialize(server) - self.conn.set_option(ldap.OPT_REFERRALS, 0) - click.echo("Connecing to %s using Kerberos ticket cache from %s" % - (server, config.LDAP_GSSAPI_CRED_CACHE)) - self.conn.sasl_interactive_bind_s('', ldap.sasl.gssapi()) - return self.conn - raise ValueError("No LDAP servers specified!") + self.conn = ldap.initialize(config.LDAP_ACCOUNTS_URI) + self.conn.set_option(ldap.OPT_REFERRALS, 0) + click.echo("Connecing to %s using Kerberos ticket cache from %s" % + (config.LDAP_ACCOUNTS_URI, config.LDAP_GSSAPI_CRED_CACHE)) + self.conn.sasl_interactive_bind_s('', ldap.sasl.gssapi()) + return self.conn def __exit__(self, type, value, traceback): - self.conn.unbind_s + self.conn.unbind_s() class ActiveDirectoryUserManager(object):