mirror of
https://github.com/quantum5/nginx-krbauth.git
synced 2025-04-24 12:32:03 -04:00
Implement HTTP basic authentication support via LDAP
This commit is contained in:
parent
7c3293ed27
commit
429ba3ff48
|
@ -29,6 +29,8 @@ LDAP_SERVER = os.environ['KRBAUTH_LDAP_SERVER']
|
||||||
LDAP_BIND_DN = os.environ.get('KRBAUTH_LDAP_BIND_DN')
|
LDAP_BIND_DN = os.environ.get('KRBAUTH_LDAP_BIND_DN')
|
||||||
LDAP_BIND_AUTHTOK = os.environ.get('KRBAUTH_LDAP_BIND_AUTHTOK')
|
LDAP_BIND_AUTHTOK = os.environ.get('KRBAUTH_LDAP_BIND_AUTHTOK')
|
||||||
LDAP_SEARCH_BASE = os.environ['KRBAUTH_LDAP_SEARCH_BASE']
|
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')
|
GSSAPI_NAME = os.environ.get('KRBAUTH_GSSAPI_NAME')
|
||||||
if GSSAPI_NAME:
|
if GSSAPI_NAME:
|
||||||
|
@ -76,9 +78,9 @@ def verify_cookie(cookie, context):
|
||||||
return hmac.compare_digest(expected, signature)
|
return hmac.compare_digest(expected, signature)
|
||||||
|
|
||||||
|
|
||||||
def make_401(reason, context, auth='Negotiate', krb5_name=None):
|
def make_401(reason, context, auth='Negotiate', **kwargs):
|
||||||
app.logger.info('Returning unauthorized: %s (krb5_name=%s, ldap_group=%s)', reason, krb5_name, context.ldap_group)
|
app.logger.info('Returning unauthorized: %s (%s)', reason, kwargs)
|
||||||
return Response('''\
|
resp = Response('''\
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>401 Unauthorized</title>
|
<title>401 Unauthorized</title>
|
||||||
|
@ -89,17 +91,20 @@ def make_401(reason, context, auth='Negotiate', krb5_name=None):
|
||||||
<center>%s</center>
|
<center>%s</center>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
''' % (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_success(context, next_url):
|
||||||
def auth():
|
resp = redirect(next_url, code=307)
|
||||||
next = request.args.get('next', '/')
|
resp.set_cookie('krbauth', make_cookie(context), secure=COOKIE_SECURE, httponly=True, samesite='Strict')
|
||||||
context = Context.from_request()
|
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:
|
try:
|
||||||
in_token = base64.b64decode(request.headers['Authorization'][10:])
|
in_token = base64.b64decode(request.headers['Authorization'][10:])
|
||||||
except binascii.Error:
|
except binascii.Error:
|
||||||
|
@ -110,7 +115,7 @@ def auth():
|
||||||
out_token = krb5_ctx.step(in_token)
|
out_token = krb5_ctx.step(in_token)
|
||||||
|
|
||||||
if not krb5_ctx.complete:
|
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
|
krb5_name = krb5_ctx._inquire(initiator_name=True).initiator_name
|
||||||
except (GSSError, GeneralError) as e:
|
except (GSSError, GeneralError) as e:
|
||||||
|
@ -121,19 +126,52 @@ def auth():
|
||||||
if LDAP_BIND_DN and LDAP_BIND_AUTHTOK:
|
if LDAP_BIND_DN and LDAP_BIND_AUTHTOK:
|
||||||
ldap_ctx.bind_s(LDAP_BIND_DN, LDAP_BIND_AUTHTOK, ldap.AUTH_SIMPLE)
|
ldap_ctx.bind_s(LDAP_BIND_DN, LDAP_BIND_AUTHTOK, ldap.AUTH_SIMPLE)
|
||||||
ldap_filter = '(&(memberOf=%s)(krbPrincipalName=%s))' % (context.ldap_group, krb5_name)
|
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'])
|
||||||
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)
|
|
||||||
if not result:
|
if not result:
|
||||||
return make_401('Did not find LDAP group member', context, krb5_name=krb5_name)
|
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:
|
else:
|
||||||
app.logger.info('Authenticated as: %s', krb5_name)
|
app.logger.info('Authenticated via Kerberos as: %s', krb5_name)
|
||||||
|
|
||||||
resp = redirect(next, code=307)
|
return auth_success(context, next_url)
|
||||||
resp.set_cookie('krbauth', make_cookie(context), secure=COOKIE_SECURE, httponly=True, samesite='Strict')
|
|
||||||
return resp
|
|
||||||
|
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')
|
@app.route('/krbauth/check')
|
||||||
|
|
Loading…
Reference in a new issue