From 23837ea33e62d279a039931f9cee781112b2f3ea Mon Sep 17 00:00:00 2001 From: "B. Stack" Date: Thu, 24 Jun 2021 11:35:00 -0400 Subject: add dns-based ldap domain controller lookup and rotate through the returned list of servers, per request! --- session_app.py.publish | 25 ++++++++++++++++++++++--- session_ldap.py | 20 ++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/session_app.py.publish b/session_app.py.publish index adfd0ad..fd403c2 100755 --- a/session_app.py.publish +++ b/session_app.py.publish @@ -10,11 +10,11 @@ # future: https://code.tutsplus.com/tutorials/flask-authentication-with-ldap--cms-23101 # better timeout session: https://stackoverflow.com/a/49891626/3569534 # store "formdata" in session for changing the basic auth to form data for the ldap login https://stackoverflow.com/a/56904875/3569534 +# modify url from urlparse https://stackoverflow.com/a/21629125/3569534 # Improve: # move all configs to config file # move all references to references section # accept a bind credential so we can perform lookups of users who match "uid=%s" under a basedn. -# accept a ldap dns domain name, and a SRV lookup for _tcp._ldap # Run: # FLASK_APP=session_app.py FLASK_DEBUG=1 flask run --host 0.0.0.0 # Dependencies: @@ -28,6 +28,7 @@ from functools import wraps import binascii, datetime import os import session_ldap +from urllib.parse import urlparse DEBUG=True app = Flask(__name__) @@ -36,7 +37,7 @@ app.debug=True secret_key_value = os.urandom(24) secret_key_value_hex_encoded = binascii.hexlify(secret_key_value) app.config['SECRET_KEY'] = secret_key_value_hex_encoded -app.config['LDAP_URI'] = "ldaps://dns1.ipa.internal.com:636" +app.config['LDAP_URI'] = "ldaps://ipa.internal.com:636" app.config['LDAP_USER_BASEDN'] = "cn=users,cn=accounts,dc=ipa,dc=internal,dc=com" app.config['LDAP_GROUP_BASEDN'] = "cn=groups,cn=accounts,dc=ipa,dc=internal,dc=com" app.config['LDAP_USER_FORMAT'] = "uid=%s,cn=users,cn=accounts,dc=ipa,dc=internal,dc=com" @@ -183,8 +184,26 @@ def login(user="None"): def ldap_login(username,password): #print(f"DEBUG: Trying user {username} with pw '{password}'") + # on first ldap_login attempt, cache this lookup result: + if 'LDAP_HOSTS' not in app.config: + this_domain = urlparse(app.config['LDAP_URI']).hostname + app.config['LDAP_HOSTS'] = session_ldap.list_ldap_servers_for_domain(this_domain) + else: + # rotate them! So every ldap_login attempt will use the next ldap server in the list. + this_list = app.config['LDAP_HOSTS'] + a = this_list[0] + this_list.append(a) + this_list.pop(0) + app.config['LDAP_HOSTS'] = this_list + # construct a new, full uri. + this_netloc = app.config['LDAP_HOSTS'][0] + up = urlparse(app.config['LDAP_URI']) + if up.port: + this_netloc += f":{up.port}" + this_uri = up._replace(netloc=this_netloc).geturl() + # Perform the ldap interactions user = session_ldap.authenticated_user( - app.config['LDAP_URI'], + this_uri, app.config['LDAP_USER_FORMAT'], username, password diff --git a/session_ldap.py b/session_ldap.py index b478ef5..d12f008 100644 --- a/session_ldap.py +++ b/session_ldap.py @@ -28,3 +28,23 @@ def authenticated_user(server_uri, user_format, username, password): # print("Either an ldap password is required, or we had another bind error.") # return False return False + +def list_ldap_servers_for_domain(domain): + # return list of hostnames from the _ldap._tcp.{domain} SRV lookup + try: + import dns + import dns.resolver + except: + print("Need python3-dns installed for dns lookups.") + return [domain] + namelist = [] + try: + query = dns.resolver.query(f"_ldap._tcp.{domain}","SRV") + except dns.resolver.NXDOMAIN: + # no records exist that match the request, so we were probably given a specific hostname, and an empty query will trigger the logic below that will add the original domain to the list. + query = [] + for i in query: + namelist.append(i.target.to_text().rstrip(".")) + if not len(namelist): + namelist.append(domain) + return namelist -- cgit