From 39e2d03f40203cdf3a2ed20e0be74dca9584e16a Mon Sep 17 00:00:00 2001 From: Khaled Elkhawaga Date: Tue, 14 Apr 2020 09:35:00 +0200 Subject: [PATCH] initial commit --- .gitignore | 114 +++++++++++++++++++++++++++++++++++++++++++++++ Dockerfile | 13 ++++++ README.md | 1 + main.py | 56 +++++++++++++++++++++++ requirements.txt | 5 +++ 5 files changed, 189 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 main.py create mode 100644 requirements.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ee540c --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Created by https://www.gitignore.io/api/flask +# Edit at https://www.gitignore.io/?templates=flask + +### Flask ### +instance/* +!instance/.gitignore +.webassets-cache + +### Flask.Python Stack ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# End of https://www.gitignore.io/api/flask diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..c1cdbd7 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.7-slim + +ENV PYTHONUNBUFFERED=1 +ENV GUNICORN_CMD_ARGS="--bind=0.0.0.0:8080 --access-logfile=-" + +WORKDIR /app + +COPY requirements.txt ./ +RUN pip install -r requirements.txt + +COPY . . + +CMD ["gunicorn", "main:app"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..608e3f6 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Based on https://developers.cloudflare.com/access/setting-up-access/validate-jwt-tokens/ \ No newline at end of file diff --git a/main.py b/main.py new file mode 100644 index 0000000..0c5beb7 --- /dev/null +++ b/main.py @@ -0,0 +1,56 @@ +from flask import Flask, request +import requests +import jwt +import json +import os +app = Flask(__name__) + +# Your CF Access Authentication domain +AUTH_DOMAIN = os.getenv("AUTH_DOMAIN") +CERTS_URL = "{}/cdn-cgi/access/certs".format(AUTH_DOMAIN) + +def _get_public_keys(): + """ + Returns: + List of RSA public keys usable by PyJWT. + """ + r = requests.get(CERTS_URL) + public_keys = [] + jwk_set = r.json() + for key_dict in jwk_set['keys']: + public_key = jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key_dict)) + public_keys.append(public_key) + return public_keys + +@app.route('/token/validate') +def verify_token(): + if 'aud' in request.args: + aud = request.args['aud'] + else: + return "missing required application audience (AUD) tag", 400 + + token = '' + if 'CF_Authorization' in request.cookies: + token = request.cookies['CF_Authorization'] + else: + return "missing required cf authorization token", 403 + keys = _get_public_keys() + + # Loop through the keys since we can't pass the key set to the decoder + valid_token = False + for key in keys: + try: + # decode returns the claims that has the email when needed + jwt.decode(token, key=key, audience=aud) + valid_token = True + break + except: + pass + if not valid_token: + return "invalid token", 403 + + return "OK", 200 + + +if __name__ == '__main__': + app.run() diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..6e95cab --- /dev/null +++ b/requirements.txt @@ -0,0 +1,5 @@ +flask +requests +cryptography +PyJWT +gunicorn