From 2657852ee3e97ee2dc233c932bd7c88bceec94b1 Mon Sep 17 00:00:00 2001 From: Scragly <29337040+scragly@users.noreply.github.com> Date: Sun, 20 Jan 2019 20:42:34 +1000 Subject: Remove RMQ, Add API POST request method. --- snekbox.py | 65 +++++++++++++++++++++++++++++--------------------------------- 1 file changed, 30 insertions(+), 35 deletions(-) (limited to 'snekbox.py') diff --git a/snekbox.py b/snekbox.py index ddde563..4e3e4fa 100644 --- a/snekbox.py +++ b/snekbox.py @@ -1,17 +1,14 @@ -import json -import multiprocessing import subprocess import os import sys -from rmq import Rmq +from flask import Flask, render_template, request, jsonify class Snekbox(object): def __init__(self, nsjail_binary='nsjail', python_binary=os.path.dirname(sys.executable)+os.sep+'python3.6'): - self.nsjail_binary = nsjail_binary self.python_binary = python_binary self.nsjail_workaround() @@ -83,34 +80,32 @@ class Snekbox(object): return output - def execute(self, body): - msg = body.decode('utf-8') - result = '' - snek_msg = json.loads(msg) - snekid = snek_msg['snekid'] - snekcode = snek_msg['message'].strip() - - result = self.python3(snekcode) - - rmq.publish(result, - queue=snekid, - routingkey=snekid, - exchange=snekid) - exit(0) - - def message_handler(self, ch, method, properties, body, thread_ws=None): - p = multiprocessing.Process(target=self.execute, args=(body,)) - p.daemon = True - p.start() - - ch.basic_ack(delivery_tag=method.delivery_tag) - - -if __name__ == '__main__': - try: - rmq = Rmq() - snkbx = Snekbox() - rmq.consume(callback=snkbx.message_handler) - except KeyboardInterrupt: - print('Exited') - exit(0) + +snekbox = Snekbox() + +# Load app +app = Flask(__name__) +app.use_reloader = False + +# Logging +log = app.logger + + +@app.route('/') +def index(): + return render_template('index.html') + + +@app.route('/result', methods=["POST", "GET"]) +def result(): + if request.method == "POST": + code = request.form["Code"] + output = snekbox.python3(code) + return render_template('result.html', code=code, result=output) + + +@app.route('/input', methods=["POST"]) +def code_input(): + body = request.get_json() + output = snekbox.python3(body["code"]) + return jsonify(input=body["code"], output=output) -- cgit v1.2.3 From 3f679bf41341a2b2546ae667998bd5ea5d80e3fe Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 25 Mar 2019 11:52:41 -0700 Subject: Add docstrings to routes --- snekbox.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'snekbox.py') diff --git a/snekbox.py b/snekbox.py index 5946e12..bb04987 100644 --- a/snekbox.py +++ b/snekbox.py @@ -109,11 +109,15 @@ log = app.logger @app.route('/') def index(): + """Return a page with a form for inputting code to be executed.""" + return render_template('index.html') @app.route('/result', methods=["POST", "GET"]) def result(): + """Execute code and return a page displaying the results.""" + if request.method == "POST": code = request.form["Code"] output = snekbox.python3(code) @@ -122,6 +126,8 @@ def result(): @app.route('/input', methods=["POST"]) def code_input(): + """Execute code and return the results.""" + body = request.get_json() output = snekbox.python3(body["code"]) return jsonify(input=body["code"], output=output) -- cgit v1.2.3 From 6b5b430c7fd0ed0398ca8ddbb76ae71b8e3d005f Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 25 Mar 2019 12:03:06 -0700 Subject: Fix import order --- snekbox.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'snekbox.py') diff --git a/snekbox.py b/snekbox.py index bb04987..65fc4b3 100644 --- a/snekbox.py +++ b/snekbox.py @@ -2,7 +2,7 @@ import os import subprocess import sys -from flask import Flask, render_template, request, jsonify +from flask import Flask, jsonify, render_template, request class Snekbox: -- cgit v1.2.3 From 0e09d10281798dd365364a12af4487fc150844c1 Mon Sep 17 00:00:00 2001 From: MarkKoz Date: Mon, 25 Mar 2019 12:36:33 -0700 Subject: Restructure project layout * Move all code into a "snekbox" package * Use logging code as __init__.py * Rename Snekbox class to NsJail * Create "site" sub-package * Move templates into this sub-package * Move Flask code into a new snekapp module --- .flake8 | 2 +- Pipfile | 2 +- logs.py | 10 --- snekbox.py | 133 ------------------------------------- snekbox/__init__.py | 10 +++ snekbox/nsjail.py | 95 ++++++++++++++++++++++++++ snekbox/site/snekapp.py | 38 +++++++++++ snekbox/site/templates/index.html | 14 ++++ snekbox/site/templates/result.html | 9 +++ templates/index.html | 14 ---- templates/result.html | 9 --- tests/test_snekbox.py | 16 ++--- 12 files changed, 176 insertions(+), 176 deletions(-) delete mode 100644 logs.py delete mode 100644 snekbox.py create mode 100644 snekbox/__init__.py create mode 100644 snekbox/nsjail.py create mode 100644 snekbox/site/snekapp.py create mode 100644 snekbox/site/templates/index.html create mode 100644 snekbox/site/templates/result.html delete mode 100644 templates/index.html delete mode 100644 templates/result.html (limited to 'snekbox.py') diff --git a/.flake8 b/.flake8 index cc5f423..c897cb6 100644 --- a/.flake8 +++ b/.flake8 @@ -1,6 +1,6 @@ [flake8] max-line-length=100 -application_import_names=snekbox,config,logs +application_import_names=snekbox ignore= P102,B311,W503,E226,S311, # Missing Docstrings diff --git a/Pipfile b/Pipfile index 221263d..3f67b54 100644 --- a/Pipfile +++ b/Pipfile @@ -29,7 +29,7 @@ lint = "flake8" precommit = "pre-commit install" test = "pytest tests --cov . --cov-report term-missing -v" report = "pytest tests --cov . --cov-report=html" -snekbox = "gunicorn -w 2 -b 0.0.0.0:8060 snekbox:app" +snekbox = "gunicorn -w 2 -b 0.0.0.0:8060 snekbox.site.snekapp:app" buildbox = "docker build -t pythondiscord/snekbox:latest -f docker/Dockerfile ." pushbox = "docker push pythondiscord/snekbox:latest" buildboxbase = "docker build -t pythondiscord/snekbox-base:latest -f docker/base.Dockerfile ." diff --git a/logs.py b/logs.py deleted file mode 100644 index fc6070e..0000000 --- a/logs.py +++ /dev/null @@ -1,10 +0,0 @@ -import logging -import sys - -logformat = logging.Formatter(fmt='[%(asctime)s] [%(process)s] [%(levelname)s] %(message)s', - datefmt='%Y-%m-%d %H:%M:%S %z') -log = logging.getLogger(__name__) -log.setLevel(logging.DEBUG) -console = logging.StreamHandler(sys.stdout) -console.setFormatter(logformat) -log.addHandler(console) diff --git a/snekbox.py b/snekbox.py deleted file mode 100644 index 65fc4b3..0000000 --- a/snekbox.py +++ /dev/null @@ -1,133 +0,0 @@ -import os -import subprocess -import sys - -from flask import Flask, jsonify, render_template, request - - -class Snekbox: - """Core snekbox functionality, providing safe execution of Python code.""" - - def __init__(self, - nsjail_binary='nsjail', - python_binary=os.path.dirname(sys.executable) + os.sep + 'python3.6'): - self.nsjail_binary = nsjail_binary - self.python_binary = python_binary - self._nsjail_workaround() - - env = { - 'PATH': ( - '/snekbox/.venv/bin:/usr/local/bin:/usr/local/' - 'sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' - ), - 'LANG': 'en_US.UTF-8', - 'PYTHON_VERSION': '3.6.5', - 'PYTHON_PIP_VERSION': '10.0.1', - 'PYTHONDONTWRITEBYTECODE': '1', - } - - def _nsjail_workaround(self): - dirs = ['/sys/fs/cgroup/pids/NSJAIL', '/sys/fs/cgroup/memory/NSJAIL'] - for d in dirs: - if not os.path.exists(d): - os.makedirs(d) - - def python3(self, cmd): - """ - Execute Python 3 code in a isolated environment. - - The value of ``cmd`` is passed using '-c' to a Python - interpreter that is started in a ``nsjail``, isolating it - from the rest of the system. - - Returns the output of executing the command (stdout) if - successful, or a error message if the execution failed. - """ - - args = [self.nsjail_binary, '-Mo', - '--rlimit_as', '700', - '--chroot', '/', - '-E', 'LANG=en_US.UTF-8', - '-R/usr', '-R/lib', '-R/lib64', - '--user', 'nobody', - '--group', 'nogroup', - '--time_limit', '2', - '--disable_proc', - '--iface_no_lo', - '--cgroup_pids_max=1', - '--cgroup_mem_max=52428800', - '--quiet', '--', - self.python_binary, '-ISq', '-c', cmd] - try: - proc = subprocess.Popen(args, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, - env=self.env, - universal_newlines=True) - except ValueError: - return 'ValueError: embedded null byte' - - stdout, stderr = proc.communicate() - if proc.returncode == 0: - output = stdout - - elif proc.returncode == 1: - try: - filtered = [] - for line in stderr.split('\n'): - if not line.startswith('['): - filtered.append(line) - output = '\n'.join(filtered) - except IndexError: - output = '' - - elif proc.returncode == 109: - return 'timed out or memory limit exceeded' - - elif proc.returncode == 255: - return 'permission denied (root required)' - - elif proc.returncode: - return f'unknown error, code: {proc.returncode}' - - else: - return 'unknown error, no error code' - - return output - - -snekbox = Snekbox() - -# Load app -app = Flask(__name__) -app.use_reloader = False - -# Logging -log = app.logger - - -@app.route('/') -def index(): - """Return a page with a form for inputting code to be executed.""" - - return render_template('index.html') - - -@app.route('/result', methods=["POST", "GET"]) -def result(): - """Execute code and return a page displaying the results.""" - - if request.method == "POST": - code = request.form["Code"] - output = snekbox.python3(code) - return render_template('result.html', code=code, result=output) - - -@app.route('/input', methods=["POST"]) -def code_input(): - """Execute code and return the results.""" - - body = request.get_json() - output = snekbox.python3(body["code"]) - return jsonify(input=body["code"], output=output) diff --git a/snekbox/__init__.py b/snekbox/__init__.py new file mode 100644 index 0000000..fc6070e --- /dev/null +++ b/snekbox/__init__.py @@ -0,0 +1,10 @@ +import logging +import sys + +logformat = logging.Formatter(fmt='[%(asctime)s] [%(process)s] [%(levelname)s] %(message)s', + datefmt='%Y-%m-%d %H:%M:%S %z') +log = logging.getLogger(__name__) +log.setLevel(logging.DEBUG) +console = logging.StreamHandler(sys.stdout) +console.setFormatter(logformat) +log.addHandler(console) diff --git a/snekbox/nsjail.py b/snekbox/nsjail.py new file mode 100644 index 0000000..458a94e --- /dev/null +++ b/snekbox/nsjail.py @@ -0,0 +1,95 @@ +import os +import subprocess +import sys + + +class NsJail: + """Core Snekbox functionality, providing safe execution of Python code.""" + + def __init__(self, + nsjail_binary='nsjail', + python_binary=os.path.dirname(sys.executable) + os.sep + 'python3.6'): + self.nsjail_binary = nsjail_binary + self.python_binary = python_binary + self._nsjail_workaround() + + env = { + 'PATH': ( + '/snekbox/.venv/bin:/usr/local/bin:/usr/local/' + 'sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' + ), + 'LANG': 'en_US.UTF-8', + 'PYTHON_VERSION': '3.6.5', + 'PYTHON_PIP_VERSION': '10.0.1', + 'PYTHONDONTWRITEBYTECODE': '1', + } + + def _nsjail_workaround(self): + dirs = ['/sys/fs/cgroup/pids/NSJAIL', '/sys/fs/cgroup/memory/NSJAIL'] + for d in dirs: + if not os.path.exists(d): + os.makedirs(d) + + def python3(self, cmd): + """ + Execute Python 3 code in a isolated environment. + + The value of ``cmd`` is passed using '-c' to a Python + interpreter that is started in a ``nsjail``, isolating it + from the rest of the system. + + Returns the output of executing the command (stdout) if + successful, or a error message if the execution failed. + """ + + args = [self.nsjail_binary, '-Mo', + '--rlimit_as', '700', + '--chroot', '/', + '-E', 'LANG=en_US.UTF-8', + '-R/usr', '-R/lib', '-R/lib64', + '--user', 'nobody', + '--group', 'nogroup', + '--time_limit', '2', + '--disable_proc', + '--iface_no_lo', + '--cgroup_pids_max=1', + '--cgroup_mem_max=52428800', + '--quiet', '--', + self.python_binary, '-ISq', '-c', cmd] + try: + proc = subprocess.Popen(args, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + env=self.env, + universal_newlines=True) + except ValueError: + return 'ValueError: embedded null byte' + + stdout, stderr = proc.communicate() + if proc.returncode == 0: + output = stdout + + elif proc.returncode == 1: + try: + filtered = [] + for line in stderr.split('\n'): + if not line.startswith('['): + filtered.append(line) + output = '\n'.join(filtered) + except IndexError: + output = '' + + elif proc.returncode == 109: + return 'timed out or memory limit exceeded' + + elif proc.returncode == 255: + return 'permission denied (root required)' + + elif proc.returncode: + return f'unknown error, code: {proc.returncode}' + + else: + return 'unknown error, no error code' + + return output diff --git a/snekbox/site/snekapp.py b/snekbox/site/snekapp.py new file mode 100644 index 0000000..492d703 --- /dev/null +++ b/snekbox/site/snekapp.py @@ -0,0 +1,38 @@ +from flask import Flask, jsonify, render_template, request + +from snekbox.nsjail import NsJail + +nsjail = NsJail() + +# Load app +app = Flask(__name__) +app.use_reloader = False + +# Logging +log = app.logger + + +@app.route('/') +def index(): + """Return a page with a form for inputting code to be executed.""" + + return render_template('index.html') + + +@app.route('/result', methods=["POST", "GET"]) +def result(): + """Execute code and return a page displaying the results.""" + + if request.method == "POST": + code = request.form["Code"] + output = nsjail.python3(code) + return render_template('result.html', code=code, result=output) + + +@app.route('/input', methods=["POST"]) +def code_input(): + """Execute code and return the results.""" + + body = request.get_json() + output = nsjail.python3(body["code"]) + return jsonify(input=body["code"], output=output) diff --git a/snekbox/site/templates/index.html b/snekbox/site/templates/index.html new file mode 100644 index 0000000..41980d1 --- /dev/null +++ b/snekbox/site/templates/index.html @@ -0,0 +1,14 @@ + + + + snekboxweb + +
+

Code:

+

+
+ + diff --git a/snekbox/site/templates/result.html b/snekbox/site/templates/result.html new file mode 100644 index 0000000..e339605 --- /dev/null +++ b/snekbox/site/templates/result.html @@ -0,0 +1,9 @@ + + + + snekboxweb + +

Code Evaluated:

{{ code }}

+

Results:

{{ result }}

+ + diff --git a/templates/index.html b/templates/index.html deleted file mode 100644 index 41980d1..0000000 --- a/templates/index.html +++ /dev/null @@ -1,14 +0,0 @@ - - - - snekboxweb - -
-

Code:

-

-
- - diff --git a/templates/result.html b/templates/result.html deleted file mode 100644 index e339605..0000000 --- a/templates/result.html +++ /dev/null @@ -1,9 +0,0 @@ - - - - snekboxweb - -

Code Evaluated:

{{ code }}

-

Results:

{{ result }}

- - diff --git a/tests/test_snekbox.py b/tests/test_snekbox.py index cc79a2a..c08178f 100644 --- a/tests/test_snekbox.py +++ b/tests/test_snekbox.py @@ -1,20 +1,20 @@ import unittest -from snekbox import Snekbox +from snekbox.nsjail import NsJail -snek = Snekbox() +nsjail = NsJail() class SnekTests(unittest.TestCase): def test_nsjail(self): - result = snek.python3('print("test")') + result = nsjail.python3('print("test")') self.assertEquals(result.strip(), 'test') # def test_memory_error(self): # code = ('x = "*"\n' # 'while True:\n' # ' x = x * 99\n') - # result = snek.python3(code) + # result = nsjail.python3(code) # self.assertEquals(result.strip(), 'timed out or memory limit exceeded') def test_timeout(self): @@ -27,13 +27,13 @@ class SnekTests(unittest.TestCase): ' continue\n' ) - result = snek.python3(code) + result = nsjail.python3(code) self.assertEquals(result.strip(), 'timed out or memory limit exceeded') def test_kill(self): code = ('import subprocess\n' 'print(subprocess.check_output("kill -9 6", shell=True).decode())') - result = snek.python3(code) + result = nsjail.python3(code) if 'ModuleNotFoundError' in result.strip(): self.assertIn('ModuleNotFoundError', result.strip()) else: @@ -43,7 +43,7 @@ class SnekTests(unittest.TestCase): code = ('import os\n' 'while 1:\n' ' os.fork()') - result = snek.python3(code) + result = nsjail.python3(code) self.assertIn('Resource temporarily unavailable', result.strip()) def test_juan_golf(self): # in honour of Juan @@ -52,5 +52,5 @@ class SnekTests(unittest.TestCase): "bytecode = CodeType(0,1,0,0,0,b'',(),(),(),'','',1,b'')\n" "exec(bytecode)") - result = snek.python3(code) + result = nsjail.python3(code) self.assertEquals('unknown error, code: 111', result.strip()) -- cgit v1.2.3