diff options
-rw-r--r-- | .gitignore | 4 | ||||
-rw-r--r-- | INTERACT.md | 49 | ||||
-rwxr-xr-x | session_app.py.publish | 190 | ||||
-rw-r--r-- | templates/index.html | 10 | ||||
-rw-r--r-- | templates/view.html | 9 |
5 files changed, 262 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d6b427a --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +__pycache__ +session_app.py +*.keytab +*.log diff --git a/INTERACT.md b/INTERACT.md new file mode 100644 index 0000000..b3c3914 --- /dev/null +++ b/INTERACT.md @@ -0,0 +1,49 @@ +Start server in a separate shell session. + + $ FLASK_APP=session_app.py FLASK_DEBUG=1 flask run --host 0.0.0.0 + +Reset any cookies and kerberos tickets. + + $ kdestroy -A + $ rm ~/cookiejar.txt + +Try visiting protected page without authorization. + + $ curl -L http://d2-03a.ipa.example.com:5000/protected -b ~/cookiejar.txt -c ~/cookiejar.txt + requires session + +Get kerberos ticket and then visit login url. This /login redirects to /login/kerberos by default. + + $ kinit ${USER} + $ klist + Ticket cache: FILE:/tmp/krb5cc_960600001_Hjgmv7lby2 + Default principal: bgstack15@IPA.EXAMPLE.COM + + Valid starting Expires Service principal + 06/20/21 16:04:10 06/21/21 16:04:07 krbtgt/IPA.EXAMPLE.COM@IPA.EXAMPLE.COM + 06/20/21 16:04:15 06/21/21 16:04:07 HTTP/d2-03a.ipa.example.com@IPA.EXAMPLE.COM + + $ curl -L http://d2-03a.ipa.example.com:5000/login --negotiate -u ':' -b ~/cookiejar.txt -c ~/cookiejar.txt + <meta http-equiv="Refresh" content="1; url=/protected/">success with kerberos + +Visit protected page now that we have a session. + + $ cat ~/cookiejar.txt + # Netscape HTTP Cookie File + # https://curl.se/docs/http-cookies.html + # This file was generated by libcurl! Edit at your own risk. + + d2-03a.ipa.example.com FALSE / FALSE 0 user "bgstack15@IPA.EXAMPLE.COM" + d2-03a.ipa.example.com FALSE / FALSE 0 type kerberos + d2-03a.ipa.example.com FALSE / FALSE 0 timestamp 2021-06-20T20:06:15Z + #HttpOnly_d2-03a.ipa.example.com FALSE / FALSE 1624219691 session eyJfcGVybWFuZW50Ijp0cnVlLCJlbmRfdGltZSI6IjIwMjEtMDYtMjBUMjA6MDY6MTVaIiwidXNlciI6ImJnaXJ0b25ASVBBLlNNSVRIMTIyLkNPTSJ9.YM-fsw.ZeI4ec-d7D64IEJ9Ab4RfpXfLt4 + + $ curl -L http://d2-03a.ipa.example.com:5000/protected -b ~/cookiejar.txt -c ~/cookiejar.txt + <html> + <title>View Session Cookie</title> + Username: bgstack15@IPA.EXAMPLE.COM<br/> + Session expires: 2021-06-20T20:06:15Z<br/> + Logged in through: kerberos + </html> + +2021-06-20 ldap basic auth, and a login form are still pending. diff --git a/session_app.py.publish b/session_app.py.publish new file mode 100755 index 0000000..915693a --- /dev/null +++ b/session_app.py.publish @@ -0,0 +1,190 @@ +#!/usr/bin/env python +# Startdate: 2021-06-17 +# goals: +# accept kerberos or ldap "authorization: basic gowinablz;nuiowekj==" auth, to create a cookie for a session that lasts for 15 minutes. use the cookie to get to protected URLs +# References: +# https://code.tutsplus.com/tutorials/flask-authentication-with-ldap--cms-23101 +# https://www.techlifediary.com/python-web-development-tutorial-using-flask-session-cookies/ +# delete cookie https://stackoverflow.com/a/14386413/3569534 +# timeout sessions https://stackoverflow.com/a/11785722/3569534 +# future: https://code.tutsplus.com/tutorials/flask-authentication-with-ldap--cms-23101 +# better timeout session: https://stackoverflow.com/a/49891626/3569534 +# Improve: +# purge sessions after 15 minutes? +# Run: +# FLASK_APP=session_app.py FLASK_DEBUG=1 flask run --host 0.0.0.0 +# Dependencies: +# apt-get install python3-flask +# pip3 install Flask-kerberos kerberos + +from flask import Flask, Response, redirect, url_for, render_template, request + +from flask_kerberos import init_kerberos, requires_authentication, _unauthorized, _forbidden, _gssapi_authenticate +from flask import _request_ctx_stack as stack, make_response, session +#from flask.ext.login import LoginManager +import kerberos +from functools import wraps +from socket import gethostname +import binascii, datetime + +from functools import wraps +import os + +DEBUG=True +app = Flask(__name__) +app.config.from_object(__name__) +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['PERMANENT_SESSION_LIFETIME'] = datetime.timedelta(days=7) +#session.permanent = True +minutes = 2 +app.permanent_session_lifetime=datetime.timedelta(minutes=minutes) + +def requires_session(function): + ''' + Requires a valid session, provided by cookie! + ''' + @wraps(function) + def decorated(*args, **kwargs): + if not session: + return Response("requires session",401) + else: + if 'user' not in session: + return Response("User is not in this session.",401) + s_user = session['user'] + c_user = request.cookies.get('user') + print(f"session user: {s_user}") + print(f"cookie user: {c_user}") + if session['user'] != c_user: + return Response("Wrong user for this session!.",401) + # otherwise, everything is good! + #return Response(f"session user: {s_user}<br/>cookie user: {c_user}", 200) + # return to the passed function, from https://github.com/ArtemAngelchev/flask-basicauth-ldap/blob/master/flask_basicauth_ldap.py + return function(*args,**kwargs) + # catch-all + return Response("requires session",401) + return decorated + +# imported from flask_kerberos and modified, because I want custom 401 message +def requires_authn_kerberos(function): + ''' + Require that the wrapped view function only be called by users + authenticated with Kerberos. The view function will have the authenticated + users principal passed to it as its first argument. + + :param function: flask view function + :type function: function + :returns: decorated function + :rtype: function + ''' + @wraps(function) + def decorated(*args, **kwargs): + header = request.headers.get("Authorization") + if header: + ctx = stack.top + token = ''.join(header.split()[1:]) + rc = _gssapi_authenticate(token) + if rc == kerberos.AUTH_GSS_COMPLETE: + response = function(ctx.kerberos_user, *args, **kwargs) + response = make_response(response) + if ctx.kerberos_token is not None: + response.headers['WWW-Authenticate'] = ' '.join(['negotiate', ctx.kerberos_token]) + return response + elif rc != kerberos.AUTH_GSS_CONTINUE: + return _forbidden() + return _unauthorized_kerberos() + return decorated + +def _unauthorized_kerberos(): + ''' + Indicate that authentication is required + ''' + # from https://billstclair.com/html-redirect2.html + return Response(f'<meta http-equiv="Refresh" content="4; url={url_for("login_ldap")}">Unauthorized! No kerberos auth provided. Trying <a href="{url_for("login_ldap")}">ldap</a> automatically in a moment.', 401, {'WWW-Authenticate': 'Negotiate'}) + +@app.route("/") +def index(): + return render_template('index.html') + +@app.route("/open/") +def open(): + header = request.headers.get("Authorization") + if header: + print("Header!") + token = ''.join(header.split()[1:]) + print("token",token) + print("something") + return "<html><body>here</body></html>", 200 + +@app.route("/protected/") +@requires_session +def protected_page(): + return protected_page_real() + +def protected_page_real(): + s_user = session['user'] + c_user = request.cookies.get('user') + cookie=request.cookies + print(cookie) + return render_template('view.html', c_user = c_user, s_user=s_user, cookie=cookie) + +@app.route("/login/") +#@requires_authn_kerberos +def login(user="None"): + # prefer kerberos + return redirect(url_for("login_kerberos")) + +@app.route("/login/kerberos") +@requires_authn_kerberos +def login_kerberos(user): + resp = Response(f'<meta http-equiv="Refresh" content="1; url={url_for("protected_page")}">success with kerberos') + #resp.headers['login'] = "from-kerberos" + resp.set_cookie('user',user) + resp.set_cookie('type',"kerberos") + end_time = datetime.datetime.now(datetime.timezone.utc) + app.permanent_session_lifetime + end_time_str = datetime.datetime.strftime(end_time,"%FT%TZ") + resp.set_cookie('timestamp',end_time_str) + session.permanent = True + session['user']=user + session['end_time'] = end_time_str + return resp + +# WORKHERE: ldap auth +# WIP 2021-06-18 17:42; make this unauthenticated GET send to a form. +@app.route("/login/ldap", methods=['POST','GET']) +#@app.route("/login/ldap/<user>") +def login_ldap(user = "none"): + resp = Response(f"success, from user {user}") + resp.headers['login'] = "from-ldap" + resp.set_cookie('user',user) + resp.set_cookie('type',"ldap") + session['user']=user + resp.set_cookie('timestamp',app.permanent_session_lifetime) + return resp + +@app.route("/logout") +def logout(): + resp = Response(f"logged out") + # Doing anything with session here leaves a cookie. + #session['user']="" + resp.set_cookie('user','',expires=0) + resp.set_cookie('type','',expires=0) + resp.set_cookie('session','',expires=0) + resp.set_cookie('timestamp','',expires=0) + return resp + +## This bumps the session lifetime to two minutes farther out from each web request with this session. +#@app.before_request +#def make_session_permanent(): +# session.permanent = True +# session['end_time'] = datetime.datetime.now()+app.permanent_session_lifetime + +# keytab from `/usr/sbin/ipa-getkeytab -p HTTP/d2-03a.ipa.example.com -k session.keytab` +os.environ['KRB5_KTNAME'] = "./session.keytab" +os.environ['KRB5_TRACE'] = "./kerberos.log" +init_kerberos(app, hostname="d2-03a.ipa.internal.com", service="HTTP") +if __name__ == '__main__': + init_kerberos(app, hostname="d2-03a.ipa.internal.com", service="HTTP") + app.run(host='0.0.0.0',debug=True) diff --git a/templates/index.html b/templates/index.html new file mode 100644 index 0000000..daa6893 --- /dev/null +++ b/templates/index.html @@ -0,0 +1,10 @@ +<html> +<head> +<title>Front page</title> +</head> +<body> +Welcome to this sample application! To access protected pages you need to <a href="/login/">log in</a>. Accepted methods are kerberos and ldap. + +{{ session }} +</body> +</html> diff --git a/templates/view.html b/templates/view.html new file mode 100644 index 0000000..5c14175 --- /dev/null +++ b/templates/view.html @@ -0,0 +1,9 @@ +<html> +<title>View Session Cookie</title> +Username: {{ s_user }}<br/>{# +Cookie user: {{ c_user }}<br/> +Session: {{ session }}<br/> +Cookies: {{ cookie }}<br/>#} +Session expires: {{ cookie.timestamp }}<br/> +Logged in through: {{ cookie.type }} +</html> |