From 429ba3ff48182fc92f754a4b29dedf6b6ad878ee Mon Sep 17 00:00:00 2001 From: Quantum Date: Thu, 20 Feb 2020 08:05:29 +0000 Subject: [PATCH] Implement HTTP basic authentication support via LDAP --- nginx_krbauth.py | 78 +++++++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 20 deletions(-) diff --git a/nginx_krbauth.py b/nginx_krbauth.py index 5700270..f258cc8 100644 --- a/nginx_krbauth.py +++ b/nginx_krbauth.py @@ -29,6 +29,8 @@ LDAP_SERVER = os.environ['KRBAUTH_LDAP_SERVER'] LDAP_BIND_DN = os.environ.get('KRBAUTH_LDAP_BIND_DN') LDAP_BIND_AUTHTOK = os.environ.get('KRBAUTH_LDAP_BIND_AUTHTOK') LDAP_SEARCH_BASE = os.environ['KRBAUTH_LDAP_SEARCH_BASE'] +LDAP_USER_DN = os.environ.get('KRBAUTH_LDAP_USER_DN') +assert not LDAP_USER_DN or LDAP_USER_DN.count('%s') == 1 GSSAPI_NAME = os.environ.get('KRBAUTH_GSSAPI_NAME') if GSSAPI_NAME: @@ -76,9 +78,9 @@ def verify_cookie(cookie, context): return hmac.compare_digest(expected, signature) -def make_401(reason, context, auth='Negotiate', krb5_name=None): - app.logger.info('Returning unauthorized: %s (krb5_name=%s, ldap_group=%s)', reason, krb5_name, context.ldap_group) - return Response('''\ +def make_401(reason, context, auth='Negotiate', **kwargs): + app.logger.info('Returning unauthorized: %s (%s)', reason, kwargs) + resp = Response('''\ 401 Unauthorized @@ -89,17 +91,20 @@ def make_401(reason, context, auth='Negotiate', krb5_name=None):
%s
-''' % (reason,), status=401, headers={'WWW-Authenticate': auth}) +''' % (reason,), status=401) + resp.headers.add('WWW-Authenticate', auth) + if LDAP_USER_DN: + resp.headers.add('WWW-Authenticate', 'Basic') + return resp -@app.route('/krbauth') -def auth(): - next = request.args.get('next', '/') - context = Context.from_request() +def auth_success(context, next_url): + resp = redirect(next_url, code=307) + resp.set_cookie('krbauth', make_cookie(context), secure=COOKIE_SECURE, httponly=True, samesite='Strict') + return resp - if not request.headers.get('Authorization', '').startswith('Negotiate '): - return make_401('No Authorization header sent', context) +def auth_spnego(context, next_url): try: in_token = base64.b64decode(request.headers['Authorization'][10:]) except binascii.Error: @@ -110,7 +115,7 @@ def auth(): out_token = krb5_ctx.step(in_token) if not krb5_ctx.complete: - return make_401('Negotiation in progress', context, auth='Negotiate ' + base64.b64encode(out_token)) + return make_401('Negotiation in progress', context, auth=['Negotiate ' + base64.b64encode(out_token)]) krb5_name = krb5_ctx._inquire(initiator_name=True).initiator_name except (GSSError, GeneralError) as e: @@ -121,19 +126,52 @@ def auth(): if LDAP_BIND_DN and LDAP_BIND_AUTHTOK: ldap_ctx.bind_s(LDAP_BIND_DN, LDAP_BIND_AUTHTOK, ldap.AUTH_SIMPLE) ldap_filter = '(&(memberOf=%s)(krbPrincipalName=%s))' % (context.ldap_group, krb5_name) - try: - result = ldap_ctx.search_s(LDAP_SEARCH_BASE, ldap.SCOPE_SUBTREE, ldap_filter, ['cn']) - except ldap.NO_SUCH_OBJECT: - return make_401('Did not find LDAP group member', context, krb5_name=krb5_name) + result = ldap_ctx.search_s(LDAP_SEARCH_BASE, ldap.SCOPE_SUBTREE, ldap_filter, ['cn']) if not result: return make_401('Did not find LDAP group member', context, krb5_name=krb5_name) - app.logger.info('Authenticated as: %s, %s', krb5_name, result[0][0]) + app.logger.info('Authenticated via Kerberos as: %s, %s', krb5_name, result[0][0]) else: - app.logger.info('Authenticated as: %s', krb5_name) + app.logger.info('Authenticated via Kerberos as: %s', krb5_name) - resp = redirect(next, code=307) - resp.set_cookie('krbauth', make_cookie(context), secure=COOKIE_SECURE, httponly=True, samesite='Strict') - return resp + return auth_success(context, next_url) + + +def auth_basic(context, next_url): + try: + token = base64.b64decode(request.headers['Authorization'][6:]) + username, _, password = token.decode('utf-8').partition(':') + except (binascii.Error, UnicodeDecodeError): + return Response(status=400) + + dn = LDAP_USER_DN % (username,) + ldap_ctx = ldap.initialize(LDAP_SERVER) + try: + ldap_ctx.bind_s(dn, password) + except ldap.INVALID_CREDENTIALS: + return make_401('Failed to authenticate to LDAP', context, dn=dn) + + if context.ldap_group: + if not ldap_ctx.search_s(dn, ldap.SCOPE_BASE, '(memberof=%s)' % (context.ldap_group,)): + return make_401('Did not find LDAP group member', context, dn=dn, group=context.ldap_group) + app.logger.info('Authenticated via LDAP as: %s in %s', dn, context.ldap_group) + else: + app.logger.info('Authenticated via LDAP as: %s', dn) + + return auth_success(context, next_url) + + +@app.route('/krbauth') +def auth(): + next_url = request.args.get('next', '/') + context = Context.from_request() + authorization = request.headers.get('Authorization', '') + + if authorization.startswith('Negotiate '): + return auth_spnego(context, next_url) + if LDAP_USER_DN and authorization.startswith('Basic '): + return auth_basic(context, next_url) + + return make_401('No Authorization header sent', context) @app.route('/krbauth/check')