From 804e08b40ba24bd89b5ad657a182327bbe9e02d7 Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Sun, 30 Jun 2019 23:58:22 +0200 Subject: [PATCH 1/3] app switching to swagger --- app.py | 113 +++++++++++++++------------------------------------------ 1 file changed, 29 insertions(+), 84 deletions(-) diff --git a/app.py b/app.py index a655c9a..8bfd0c4 100644 --- a/app.py +++ b/app.py @@ -1,88 +1,33 @@ -from flask import Flask,jsonify,request,abort -import json - -import base64 -import os -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import hashes -from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC - -from cryptography.fernet import Fernet - -def genKey(password_provided): - password = password_provided.encode() - salt = b'rrdX9A66qXtQZwzf' # You NEED TO CHANGE THIS (eg. os.urandom(16)) must be of type bytes - kdf = PBKDF2HMAC( - algorithm=hashes.SHA256(), - length=32, - salt=salt, - iterations=100000, - backend=default_backend() - ) - key = base64.urlsafe_b64encode(kdf.derive(password)) # Can only use kdf once - return key +from flask import Flask +from flask_swagger_ui import get_swaggerui_blueprint app = Flask(__name__) -''' -Secure Transport (TLS) is mandatory. -This snippet is minimalist on purpose so to keep processing on the memory -''' -@app.route("/", methods=('GET',)) -def hello(): - return "App is Up!" - - -@app.route("/encrypt", methods=['POST']) -def encrypt(): - ''' - { - "key":"secretKey", - "plaintext":"Text containing sensitive data to be encrypted" - } - ''' - req_data = request.get_json() - - try: - key = req_data['key'] - plaintext = req_data['plaintext'] - except: - abort(400) - - message = plaintext.encode() - - try: - f = Fernet(genKey(key)) - encrypted = f.encrypt(message) - except: - abort(403) - - return encrypted - -@app.route("/decrypt", methods=['POST']) -def decrypt(): - ''' - { - "key":"secretKey", - "ciphertext":"gAAAAABdGQQgxejDKPqkR9tMGdHsL0ewJr3z3TOZeNC7-0AxBIxCv3gjAmng4ZrIY668ovifRMl1_F_5O64Wjbhn0qsm2Vn7UjbEhOEvXFlFIaK1ichVONWHr0sMGD5s30TNf7_9LEKN" - } - ''' - req_data = request.get_json() - try: - key = req_data['key'] - ciphertext = req_data['ciphertext'] - except: - abort(400) - - encrypted = ciphertext.encode() - - try: - f = Fernet(genKey(key)) - decrypted = f.decrypt(encrypted) - except: - abort(403) - - return decrypted -if __name__ == "__main__": - app.run(host='0.0.0.0', port=5000) \ No newline at end of file +SWAGGER_URL = '/api/docs' # URL for exposing Swagger UI (without trailing '/') +API_URL = 'http://petstore.swagger.io/v2/swagger.json' # Our API url (can of course be a local resource) + +# Call factory function to create our blueprint +swaggerui_blueprint = get_swaggerui_blueprint( + SWAGGER_URL, # Swagger UI static files will be mapped to '{SWAGGER_URL}/dist/' + API_URL, + config={ # Swagger UI config overrides + 'app_name': "Test application" + }, + # oauth_config={ # OAuth config. See https://github.com/swagger-api/swagger-ui#oauth2-configuration . + # 'clientId': "your-client-id", + # 'clientSecret': "your-client-secret-if-required", + # 'realm': "your-realms", + # 'appName': "your-app-name", + # 'scopeSeparator': " ", + # 'additionalQueryStringParams': {'test': "hello"} + # } +) + +# Register blueprint at URL +# (URL must match the one given to factory function above) +app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL) + +app.run() + +# Now point your browser to localhost:5000/api/docs/ \ No newline at end of file From d6571f0a001cdee486c018a031c5f8867d54bf78 Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Tue, 2 Jul 2019 22:02:18 +0200 Subject: [PATCH 2/3] WSGI production mode --- Dockerfile | 9 ++- app.py | 145 ++++++++++++++++++++++++++++++++++++----------- requirements.txt | 6 +- start.sh | 1 + wsgi.py | 4 ++ 5 files changed, 127 insertions(+), 38 deletions(-) create mode 100755 start.sh create mode 100644 wsgi.py diff --git a/Dockerfile b/Dockerfile index e39919f..f784e94 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,13 +1,16 @@ FROM python:alpine3.10 MAINTAINER Abderrahmane SMIMITE -RUN apk add build-base openssl-dev libffi-dev vim -RUN pip install cffi cryptography flask +RUN apk add build-base openssl-dev libffi-dev vim py-gunicorn RUN mkdir /app +COPY requirements.txt /app COPY app.py /app +COPY wsgi.py /app WORKDIR /app +RUN pip install -r requirements.txt + EXPOSE 5000 -CMD ["python3", "app.py"] \ No newline at end of file +CMD ["./start.sh"] \ No newline at end of file diff --git a/app.py b/app.py index 8bfd0c4..c366881 100644 --- a/app.py +++ b/app.py @@ -1,33 +1,112 @@ -from flask import Flask -from flask_swagger_ui import get_swaggerui_blueprint - -app = Flask(__name__) - - -SWAGGER_URL = '/api/docs' # URL for exposing Swagger UI (without trailing '/') -API_URL = 'http://petstore.swagger.io/v2/swagger.json' # Our API url (can of course be a local resource) - -# Call factory function to create our blueprint -swaggerui_blueprint = get_swaggerui_blueprint( - SWAGGER_URL, # Swagger UI static files will be mapped to '{SWAGGER_URL}/dist/' - API_URL, - config={ # Swagger UI config overrides - 'app_name': "Test application" - }, - # oauth_config={ # OAuth config. See https://github.com/swagger-api/swagger-ui#oauth2-configuration . - # 'clientId': "your-client-id", - # 'clientSecret': "your-client-secret-if-required", - # 'realm': "your-realms", - # 'appName': "your-app-name", - # 'scopeSeparator': " ", - # 'additionalQueryStringParams': {'test': "hello"} - # } -) - -# Register blueprint at URL -# (URL must match the one given to factory function above) -app.register_blueprint(swaggerui_blueprint, url_prefix=SWAGGER_URL) - -app.run() - -# Now point your browser to localhost:5000/api/docs/ \ No newline at end of file +from flask import Flask,jsonify,request,abort +from flask_restplus import Resource, Api, fields, reqparse +import json + +import base64 +import os +from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC + +from cryptography.fernet import Fernet + + +flask_app = Flask(__name__) +app = Api(app = flask_app, + version = "1.0", + title = "EOF (Encryption On the Fly)", + description = "Manage real-time encryption for your app with a simple API. Based on Fernet implementation which uses 128-bit AES in CBC mode and PKCS7 padding, with HMAC using SHA256. Proof Of Concept API with NO INFORMATION STORED.") + +name_space = app.namespace('v1', description='Encryption on the fly API') + + +def genKey(password_provided): + password = password_provided.encode() + salt = b'rrdX9A66qXtQZwzf' # You NEED TO CHANGE THIS (eg. os.urandom(16)) must be of type bytes + kdf = PBKDF2HMAC( + algorithm=hashes.SHA256(), + length=32, + salt=salt, + iterations=100000, + backend=default_backend() + ) + key = base64.urlsafe_b64encode(kdf.derive(password)) # Can only use kdf once + return key + +''' +Secure Transport (TLS) is mandatory. +This snippet is minimalist on purpose so to keep processing on the memory +''' +EncryptThis = app.model('Encrypt This', { + 'key': fields.String(required=True, description='Your encryption password that will be used to derivate the key'), + 'plaintext': fields.String(required=True, description='Data to encrypt') +}) + +DecryptThis = app.model('Decrypt This', { + 'key': fields.String(required=True, description='Your encryption password used to derivate the key'), + 'ciphertext': fields.String(required=True, description='Data to decrypt') +}) + +@name_space.route('/health') +class Health(Resource): + def get(self): + '''Return system health status''' + return {'status': 'Everything looks fine'} + + +@name_space.route('/encrypt') +class Encryption(Resource): + ''' + { + "key":"secretKey", + "plaintext":"Text containing sensitive data to be encrypted" + } + ''' + @name_space.expect(EncryptThis) + def post(self): + '''Get a plaintext and the associated key and return ciphertext''' + try: + key = name_space.payload['key'] + plaintext = name_space.payload['plaintext'] + except: + abort(400) + + message = plaintext.encode() + + try: + f = Fernet(genKey(key)) + encrypted = f.encrypt(message) + except: + abort(403) + + return {'ciphertext':encrypted.decode()} + +@name_space.route('/decrypt') +class Decryption(Resource): + ''' + { + "key":"secretKey", + "ciphertext":"gAAAAABdGQQgxejDKPqkR9tMGdHsL0ewJr3z3TOZeNC7-0AxBIxCv3gjAmng4ZrIY668ovifRMl1_F_5O64Wjbhn0qsm2Vn7UjbEhOEvXFlFIaK1ichVONWHr0sMGD5s30TNf7_9LEKN" + } + ''' + @name_space.expect(DecryptThis) + def post(self): + '''Get a ciphertext and the associated key and return plaintext''' + try: + key = name_space.payload['key'] + ciphertext = name_space.payload['ciphertext'] + except: + abort(400) + + encrypted = ciphertext.encode() + + try: + f = Fernet(genKey(key)) + decrypted = f.decrypt(encrypted) + except: + abort(403) + + return {'plaintext':decrypted.decode()} + +if __name__ == '__main__': + flask_app.run(debug=False) \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index a9faa85..ddeae1b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,5 @@ -cffi==1.12.3 -cryptography==2.6.1 Flask==1.0.2 +flask_restplus==0.12.1 +cryptography==2.6.1 +cffi==1.12.3 +gunicorn==19.9.0 \ No newline at end of file diff --git a/start.sh b/start.sh new file mode 100755 index 0000000..738fb50 --- /dev/null +++ b/start.sh @@ -0,0 +1 @@ +gunicorn --bind 0.0.0.0:5000 wsgi:flask_app \ No newline at end of file diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 0000000..d95ea26 --- /dev/null +++ b/wsgi.py @@ -0,0 +1,4 @@ +from app import flask_app + +if __name__ == "__main__": + flask_app.run() \ No newline at end of file From ff682280dbfd7510b682e860def1f076d6c09f4c Mon Sep 17 00:00:00 2001 From: Abderrahmane Smimite Date: Tue, 2 Jul 2019 22:15:02 +0200 Subject: [PATCH 3/3] Fix Docker --- Dockerfile | 3 ++- start.sh | 2 ++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index f784e94..4abc0c3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,10 +7,11 @@ RUN mkdir /app COPY requirements.txt /app COPY app.py /app COPY wsgi.py /app +COPY start.sh /app WORKDIR /app RUN pip install -r requirements.txt EXPOSE 5000 -CMD ["./start.sh"] \ No newline at end of file +CMD ["/app/start.sh"] \ No newline at end of file diff --git a/start.sh b/start.sh index 738fb50..82a8825 100755 --- a/start.sh +++ b/start.sh @@ -1 +1,3 @@ +#!/usr/bin/env ash +cd /app gunicorn --bind 0.0.0.0:5000 wsgi:flask_app \ No newline at end of file