From 8633511ca0354f0ff5a19b7236611b21f5e4ef56 Mon Sep 17 00:00:00 2001 From: Ryan Bueter Date: Fri, 10 Oct 2025 15:45:02 -0600 Subject: [PATCH 1/2] (fix) Adding cookie expiration --- root/app/ldap-backend-app.py | 7 +++++-- root/app/nginx-ldap-auth-daemon.py | 17 +++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/root/app/ldap-backend-app.py b/root/app/ldap-backend-app.py index 6b72732..7711b4e 100644 --- a/root/app/ldap-backend-app.py +++ b/root/app/ldap-backend-app.py @@ -9,6 +9,7 @@ # 1) accepts GET requests on /login and /ldaplogin and responds with a login form # 2) accepts POST requests on /login and /ldaplogin, sets a cookie, and responds with redirect +import datetime import sys, os, signal, base64, cgi if sys.version_info.major == 2: from urlparse import urlparse @@ -141,7 +142,9 @@ def do_POST(self): cipher_suite = Fernet(fernetkey) enc = cipher_suite.encrypt(ensure_bytes(user + ':' + passwd)) enc = enc.decode() - self.send_header('Set-Cookie', 'nginxauth=' + enc + '; httponly') + expires = datetime.datetime.utcnow() + datetime.timedelta(weeks=1) + expires_str = expires.strftime('%a, %d-%b-%Y %H:%M:%S GMT') + self.send_header('Set-Cookie', 'nginxauth=' + enc + f'; Expires={expires_str}; httponly') self.send_header('Location', target) self.end_headers() @@ -171,4 +174,4 @@ def exit_handler(signal, frame): if __name__ == '__main__': server = AuthHTTPServer(Listen, AppHandler) signal.signal(signal.SIGINT, exit_handler) - server.serve_forever() + server.serve_forever() \ No newline at end of file diff --git a/root/app/nginx-ldap-auth-daemon.py b/root/app/nginx-ldap-auth-daemon.py index 0c5f1c6..2fca3f8 100644 --- a/root/app/nginx-ldap-auth-daemon.py +++ b/root/app/nginx-ldap-auth-daemon.py @@ -94,9 +94,15 @@ def do_GET(self): user, passwd = auth_decoded.split(':', 1) except InvalidToken: self.log_message('Incorrect token. Trying to decode credentials from BASE64...') - auth_decoded = base64.b64decode(auth_header[6:]) - auth_decoded = auth_decoded.decode("utf-8") - user, passwd = auth_decoded.split(':', 1) + # Wrapping below in try-except block to catch failed decoding due to expired cookie + try: + auth_decoded = base64.b64decode(auth_header[6:]) + auth_decoded = auth_decoded.decode("utf-8") + user, passwd = auth_decoded.split(':', 1) + except Exception as e: + self.auth_failed(ctx) + self.log_error(e) + return True except Exception as e: self.auth_failed(ctx) self.log_error(e) @@ -217,7 +223,7 @@ def do_GET(self): return ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_ALLOW) - + ctx['action'] = 'initializing LDAP connection' ldap_obj = ldap.initialize(ctx['url']); @@ -254,7 +260,7 @@ def do_GET(self): searchfilter, ['objectclass'], 1) ctx['action'] = 'verifying search query results' - + nres = len(results) if nres < 1: @@ -363,4 +369,3 @@ def exit_handler(signal, frame): sys.stdout.write("Start listening on %s:%d...\n" % Listen) sys.stdout.flush() server.serve_forever() - From 10f948cb6a2bc5ed2f8f415e3c3c802e68c260cb Mon Sep 17 00:00:00 2001 From: Ryan Bueter Date: Fri, 24 Oct 2025 17:31:11 -0600 Subject: [PATCH 2/2] Cleaned up exception handling --- .gitignore | 3 ++ root/app/ldap-backend-app.py | 5 +-- root/app/nginx-ldap-auth-daemon.py | 70 ++++++++++++++++++++++-------- 3 files changed, 56 insertions(+), 22 deletions(-) diff --git a/.gitignore b/.gitignore index 6e8ad97..58e6a70 100644 --- a/.gitignore +++ b/.gitignore @@ -42,3 +42,6 @@ Network Trash Folder Temporary Items .apdisk .jenkins-external + +# Local virtual environment +.venv/ \ No newline at end of file diff --git a/root/app/ldap-backend-app.py b/root/app/ldap-backend-app.py index 7711b4e..9f53123 100644 --- a/root/app/ldap-backend-app.py +++ b/root/app/ldap-backend-app.py @@ -9,8 +9,7 @@ # 1) accepts GET requests on /login and /ldaplogin and responds with a login form # 2) accepts POST requests on /login and /ldaplogin, sets a cookie, and responds with redirect -import datetime -import sys, os, signal, base64, cgi +import sys, os, signal, base64, cgi, datetime if sys.version_info.major == 2: from urlparse import urlparse from Cookie import BaseCookie @@ -142,7 +141,7 @@ def do_POST(self): cipher_suite = Fernet(fernetkey) enc = cipher_suite.encrypt(ensure_bytes(user + ':' + passwd)) enc = enc.decode() - expires = datetime.datetime.utcnow() + datetime.timedelta(weeks=1) + expires = datetime.datetime.now(datetime.UTC) + datetime.timedelta(weeks=1) expires_str = expires.strftime('%a, %d-%b-%Y %H:%M:%S GMT') self.send_header('Set-Cookie', 'nginxauth=' + enc + f'; Expires={expires_str}; httponly') diff --git a/root/app/nginx-ldap-auth-daemon.py b/root/app/nginx-ldap-auth-daemon.py index 2fca3f8..78ee0b9 100644 --- a/root/app/nginx-ldap-auth-daemon.py +++ b/root/app/nginx-ldap-auth-daemon.py @@ -82,37 +82,69 @@ def do_GET(self): return True - ctx['action'] = 'decoding credentials' + ctx['action'] = 'decoding credentials with fernet key' + user: str | None + passwd: str | None + error: str | None + user, passwd, error = self.decode_credentials_with_fernet_key(ctx, auth_header) + + if error == "InvalidToken": + self.log_message('Incorrect token.') + ctx['action'] = 'decoding credentials with base64' + user, passwd = self.decode_credentials_with_base64(ctx, auth_header) + + if user is None and passwd is None: + return True + + ctx['user'] = ldap.filter.escape_filter_chars(user) + ctx['pass'] = passwd + + # Continue request processing + return False + + + def decode_credentials_with_fernet_key( + self, + ctx: dict, + auth_header: str + ) -> tuple[str, str, str]: + user: str | None = None + passwd: str | None = None + error: str | None = None try: fernetkey = os.getenv("FERNET_KEY").encode() cipher_suite = Fernet(fernetkey) - self.log_message('Trying to dechipher credentials...') + self.log_message('Trying to dechipher credentials with Fernet Key...') auth_decoded = auth_header[6:].encode() auth_decoded = cipher_suite.decrypt(auth_decoded) auth_decoded = auth_decoded.decode("utf-8") user, passwd = auth_decoded.split(':', 1) - except InvalidToken: - self.log_message('Incorrect token. Trying to decode credentials from BASE64...') - # Wrapping below in try-except block to catch failed decoding due to expired cookie - try: - auth_decoded = base64.b64decode(auth_header[6:]) - auth_decoded = auth_decoded.decode("utf-8") - user, passwd = auth_decoded.split(':', 1) - except Exception as e: - self.auth_failed(ctx) - self.log_error(e) - return True + except (InvalidToken, TypeError, UnicodeDecodeError): + error = "InvalidToken" except Exception as e: self.auth_failed(ctx) - self.log_error(e) - return True + self.log_error(str(e)) + return user, passwd, error - ctx['user'] = ldap.filter.escape_filter_chars(user) - ctx['pass'] = passwd - # Continue request processing - return False + def decode_credentials_with_base64( + self, + ctx: dict, + auth_header: str + ) -> tuple[str, str]: + user: str | None = None + passwd: str | None = None + try: + self.log_message('Trying to decode credentials from BASE64...') + auth_decoded = base64.b64decode(auth_header[6:]) + auth_decoded = auth_decoded.decode("utf-8") + user, passwd = auth_decoded.split(':', 1) + except Exception as e: + self.auth_failed(ctx) + self.log_error(str(e)) + return user, passwd + def get_cookie(self, name): cookies = self.headers.get('Cookie')