Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 36 additions & 17 deletions nginx-ldap-auth-daemon
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
import os
import pwd
import grp
Expand All @@ -7,14 +7,23 @@ import signal
import base64
import ldap
import argparse
from BaseHTTPServer import HTTPServer, BaseHTTPRequestHandler
from http.server import HTTPServer, BaseHTTPRequestHandler
import logging

logger = logging.getLogger()
logger.addHandler(logging.StreamHandler())
logger.setLevel(logging.INFO)

# -----------------------------------------------------------------------------
# Requests are processed in separate thread
import threading
from SocketServer import ThreadingMixIn
from socketserver import ThreadingMixIn


class AuthHTTPServer(ThreadingMixIn, HTTPServer):
pass


# -----------------------------------------------------------------------------
# Requests are processed in separate process
# from SocketServer import ForkingMixIn
Expand All @@ -41,13 +50,13 @@ def read_conf(fname):
data = line.strip().split()
if len(data) > 1 and data[0] in opts:
conf[data[0]] = ' '.join(data[1:])
except:
print "Unable to read {} as uid {}: {}".format(fname, os.getuid(), sys.exc_info())
except Exception:
logger.critical("Unable to read {} as uid {}: {}".format(fname, os.getuid(), sys.exc_info()))
sys.exit(1)

for o in opts[:4]:
if o not in conf:
print "Mandatory parameter '{}' was not found in config file {}!".format(o, fname)
logger.critical("Mandatory parameter '{}' was not found in config file {}!".format(o, fname))
sys.exit(1)


Expand All @@ -70,19 +79,24 @@ def check_auth(user, passwd, allowusr, allowgr):
proto = 'ldap://' if conf['ssl'] != 'on' else 'ldaps://'
for host in conf['host'].split():
try:
ldap_connection = ldap.initialize(proto + host)
ldap_connection = ldap.initialize(proto + host, bytes_mode=False)
ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
ldap_connection.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW)
ldap_connection.set_option(ldap.OPT_REFERRALS, 0) # MS AD
ldap_connection.set_option(ldap.OPT_NETWORK_TIMEOUT, 3)
ldap_connection.simple_bind_s(conf['binddn'], conf['bindpw'])
data = ldap_connection.search_s(base=conf['base'], scope=ldap.SCOPE_SUBTREE, filterstr='(&(objectClass=user)(sAMAccountName=' + user + '))')
data = ldap_connection.search_s(base=conf['base'], scope=ldap.SCOPE_SUBTREE,
filterstr='(&(objectClass=user)(sAMAccountName=' + user + '))')
if data:
data = data[0][1]
# check if search found user
if 'distinguishedName' not in data:
return False
# check password
try:
ldap_connection.simple_bind_s(data['distinguishedName'][0], passwd)
ldap_connection.simple_bind_s(data['distinguishedName'][0].decode('utf-8'), passwd)
except ldap.INVALID_CREDENTIALS:
logger.warning("Login failed for user {}".format(user))
return False
# check allowed users
if allowusr and user.lower() in [x.lower().strip() for x in allowusr.split(',')]:
Expand All @@ -94,12 +108,14 @@ def check_auth(user, passwd, allowusr, allowgr):
groups += data['msSFU30PosixMemberOf']
for g in [x.lower().strip() for x in allowgr.split(',')]:
for group in groups:
if group.lower().startswith('cn={},'.format(g)):
if group.decode('utf-8').lower().startswith('cn={},'.format(g)):
return True
# user found but not in allowed
return False if allowusr or allowgr else True
except (ldap.CONNECT_ERROR, ldap.SERVER_DOWN):
pass # try next server
logger.warning('Connection error to server {}'.format(host))
# try next server
continue
finally:
if ldap_connection:
ldap_connection.unbind()
Expand All @@ -109,19 +125,21 @@ def check_auth(user, passwd, allowusr, allowgr):
class LDAPAuthHandler(BaseHTTPRequestHandler):
def do_GET(self):
try:
auth_header = self.headers.getheader('Authorization')
auth_header = self.headers.get('Authorization')
if auth_header and auth_header.lower().startswith('basic '):
user, passwd = base64.b64decode(auth_header[6:]).split(':', 1)
if check_auth(user, passwd, self.headers.getheader('X-Ldap-Allowed-Usr'), self.headers.getheader('X-Ldap-Allowed-Grp')):
user, passwd = base64.b64decode(auth_header[6:]).decode('utf-8').split(':', 1)
if check_auth(user, passwd, self.headers.get('X-Ldap-Allowed-Usr'),
self.headers.get('X-Ldap-Allowed-Grp')):
self.send_response(200)
return
self.send_response(401)
realm = self.headers.getheader('X-Ldap-Realm')
realm = self.headers.get('X-Ldap-Realm')
if not realm:
realm = 'Authorization required'
self.send_header('WWW-Authenticate', 'Basic realm="{}"'.format(realm))
self.send_header('Cache-Control', 'no-cache')
except:
except Exception:
logger.exception('Unhandled exception in LdapAuthHandler')
self.send_response(500)
self.send_header('X-Error-Message', sys.exc_info()[1])
finally:
Expand All @@ -132,7 +150,8 @@ if __name__ == '__main__':
parser = argparse.ArgumentParser(description="""Simple Nginx LDAP authentication helper.""")
parser.add_argument('--host', default="localhost", help="host to bind (Default: localhost)")
parser.add_argument('-p', '--port', type=int, default=8888, help="port to bind (Default: 8888)")
parser.add_argument('-c', '--config', default='/etc/pam_ldap.conf', help="config with LDAP creds (Default: /etc/pam_ldap.conf)")
parser.add_argument('-c', '--config', default='/etc/pam_ldap.conf',
help="config with LDAP creds (Default: /etc/pam_ldap.conf)")
args = parser.parse_args()

read_conf(args.config)
Expand Down