From 2e59286238c5801b63b3432be2c64d116693aadb Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 13:53:07 +0700 Subject: [PATCH 1/2] add docker executor --- docker/Dockerfile | 13 ++++ docker/docker-executor/app.py | 98 +++++++++++++++++++++++++ docker/docker-executor/requirements.txt | 31 ++++++++ 3 files changed, 142 insertions(+) create mode 100644 docker/Dockerfile create mode 100644 docker/docker-executor/app.py create mode 100644 docker/docker-executor/requirements.txt diff --git a/docker/Dockerfile b/docker/Dockerfile new file mode 100644 index 0000000..8a8e5f0 --- /dev/null +++ b/docker/Dockerfile @@ -0,0 +1,13 @@ +FROM python:3.8-slim-buster + +WORKDIR /executor +COPY docker-executor /executor + +ENV MAX_EXECUTABLE=8192 +ENV MAX_DATA_SIZE=512 + +EXPOSE 5000 + +RUN pip3 install -r requirements.txt + +CMD [ "gunicorn", "app:app", "-b", "0.0.0.0:5000"] diff --git a/docker/docker-executor/app.py b/docker/docker-executor/app.py new file mode 100644 index 0000000..9ba6f25 --- /dev/null +++ b/docker/docker-executor/app.py @@ -0,0 +1,98 @@ +from flask import jsonify, Flask, request +import os +import shlex +import subprocess +import base64 + +# Set environment flag of MAX_EXECUTABLE, MAX_DATA_SIZE + + +runtime_version = "google-cloud-function:2.0.3" +app = Flask(__name__) + +def get_env(env, flag): + if flag not in env: + raise Exception(flag + " is missing") + return int(env[flag]) + + +def success(returncode, stdout, stderr, err): + return ( + jsonify( + { + "returncode": returncode, + "stdout": stdout, + "stderr": stderr, + "err": err, + "version": runtime_version, + } + ), + 200, + ) + + +def bad_request(err): + return jsonify({"error": err}), 400 + +@app.route('/', methods=['POST']) +def execute(): + """Responds to any HTTP request. + Args: + request (flask.Request): HTTP request object. + Returns: + The response text or any set of values that can be turned into a + Response object using + `make_response `. + """ + env = os.environ.copy() + + MAX_EXECUTABLE = get_env(env, "MAX_EXECUTABLE") + MAX_DATA_SIZE = get_env(env, "MAX_DATA_SIZE") + + request_json = request.get_json(force=True) + if "executable" not in request_json: + return bad_request("Missing executable value") + executable = base64.b64decode(request_json["executable"]) + if len(executable) > MAX_EXECUTABLE: + return bad_request("Executable exceeds max size") + if "calldata" not in request_json: + return bad_request("Missing calldata value") + if len(request_json["calldata"]) > MAX_DATA_SIZE: + return bad_request("Calldata exceeds max size") + if "timeout" not in request_json: + return bad_request("Missing timeout value") + try: + timeout = int(request_json["timeout"]) + except ValueError: + return bad_request("Timeout format invalid") + + path = "/tmp/execute.sh" + with open(path, "w") as f: + f.write(executable.decode()) + + os.chmod(path, 0o775) + try: + env = os.environ.copy() + for key, value in request_json.get("env", {}).items(): + env[key] = value + + proc = subprocess.Popen( + [path] + shlex.split(request_json["calldata"]), + env=env, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + proc.wait(timeout=(timeout / 1000)) + returncode = proc.returncode + stdout = proc.stdout.read(MAX_DATA_SIZE).decode() + stderr = proc.stderr.read(MAX_DATA_SIZE).decode() + return success(returncode, stdout, stderr, "") + except OSError: + return success(126, "", "", "Execution fail") + except subprocess.TimeoutExpired: + return success(111, "", "", "Execution time limit exceeded") + +@app.route('/', methods=['GET']) +def hello_world(): + return 'Hello there' diff --git a/docker/docker-executor/requirements.txt b/docker/docker-executor/requirements.txt new file mode 100644 index 0000000..b2f78ca --- /dev/null +++ b/docker/docker-executor/requirements.txt @@ -0,0 +1,31 @@ +aiodns==3.0.0 +aiohttp==3.8.1 +aiosignal==1.2.0 +async-timeout==4.0.2 +asynctest==0.13.0 +attrs==21.4.0 +bech32==1.2.0 +ccxt==1.87.48 +certifi==2022.5.18.1 +cffi==1.15.0 +charset-normalizer==2.0.12 +click==8.1.3 +cryptography==37.0.2 +Flask==2.1.2 +frozenlist==1.3.0 +gunicorn==20.1.0 +idna==3.3 +importlib-metadata==4.11.4 +itsdangerous==2.1.2 +Jinja2==3.1.2 +MarkupSafe==2.1.1 +multidict==6.0.2 +pycares==4.1.2 +pycparser==2.21 +requests==2.28.0 +typing-extensions==4.4.0 +urllib3==1.26.9 +websocket-client==1.3.2 +Werkzeug==2.1.2 +yarl==1.7.2 +zipp==3.8.0 From 9f027556dba5176966ea941dafa142b3f0b1dd66 Mon Sep 17 00:00:00 2001 From: colmazia Date: Thu, 5 Jan 2023 13:54:32 +0700 Subject: [PATCH 2/2] remove get endpoint --- docker/docker-executor/app.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/docker/docker-executor/app.py b/docker/docker-executor/app.py index 9ba6f25..c335061 100644 --- a/docker/docker-executor/app.py +++ b/docker/docker-executor/app.py @@ -92,7 +92,3 @@ def execute(): return success(126, "", "", "Execution fail") except subprocess.TimeoutExpired: return success(111, "", "", "Execution time limit exceeded") - -@app.route('/', methods=['GET']) -def hello_world(): - return 'Hello there'