diff --git a/gunicorn/Makefile b/gunicorn/Makefile new file mode 100644 index 0000000..b75563e --- /dev/null +++ b/gunicorn/Makefile @@ -0,0 +1,41 @@ +# Copyright (C) 2024 Gramine contributors +# SPDX-License-Identifier: BSD-3-Clause + +ARCH_LIBDIR ?= /lib/$(shell $(CC) -dumpmachine) + +ifeq ($(DEBUG),1) +GRAMINE_LOG_LEVEL = debug +else +GRAMINE_LOG_LEVEL = error +endif + +.PHONY: all +all: gunicorn.manifest +ifeq ($(SGX),1) +all: gunicorn.manifest.sgx gunicorn.sig +endif + +gunicorn.manifest: gunicorn.manifest.template + gramine-manifest \ + -Dlog_level=$(GRAMINE_LOG_LEVEL) \ + -Darch_libdir=$(ARCH_LIBDIR) \ + -Dentrypoint=$(realpath $(shell sh -c "command -v gunicorn")) \ + -Dpython_exe_path=$(realpath $(shell sh -c "command -v python3")) \ + $< >$@ + +# Make on Ubuntu <= 20.04 doesn't support "Rules with Grouped Targets" (`&:`) +gunicorn.manifest.sgx gunicorn.sig: sgx_sign + @: + +.INTERMEDIATE: sgx_sign +sgx_sign: gunicorn.manifest + gramine-sgx-sign \ + --manifest $< \ + --output $<.sgx + +.PHONY: clean +clean: + $(RM) -rf *.token *.sig *.manifest *.manifest.sgx __pycache__ + +.PHONY: distclean +distclean: clean diff --git a/gunicorn/README.md b/gunicorn/README.md new file mode 100644 index 0000000..34ed236 --- /dev/null +++ b/gunicorn/README.md @@ -0,0 +1,48 @@ +# Gunicorn example + +This directory contains an example for running Gunicorn in Gramine, including the Makefile and a +template for generating the manifest. Gunicorn is a webserver to deploy Flask applications in +Python. Although Flask comes with an internal webserver, this is widely considered to be not +viable for production. Common practice in production is to put Flask behind a real webserver that +communicates via the WSGI protocol. A common choice for that webserver is Gunicorn. For more +documentation, refer to https://docs.gunicorn.org/en/stable/. + +# Generating the manifest + +## Installing prerequisites + +Please run the following command to install Gunicorn and its dependencies on Ubuntu 22.04: +``` +sudo apt-get install python3 python3-flask gunicorn +``` + +## Building for Linux + +Run `make` (non-debug) or `make DEBUG=1` (debug) in the directory. + +## Building for SGX + +Run `make SGX=1` (non-debug) or `make SGX=1 DEBUG=1` (debug) in the directory. + +# Running Gunicorn with Gramine + +Here's an example of running Gunicorn under Gramine: + +Without SGX: +``` +gramine-direct gunicorn +``` + +With SGX: +``` +gramine-sgx gunicorn +``` + +Because these commands will start the Gunicorn server in the foreground, you will need to open +another console to run the client. + +Once the server has started, you can test it with `curl`. + +``` +curl http://127.0.0.1:8000/hello +``` diff --git a/gunicorn/gunicorn.manifest.template b/gunicorn/gunicorn.manifest.template new file mode 100644 index 0000000..2879e5a --- /dev/null +++ b/gunicorn/gunicorn.manifest.template @@ -0,0 +1,49 @@ +# Copyright (C) 2024 Gramine contributors +# SPDX-License-Identifier: BSD-3-Clause + +loader.entrypoint = "file:{{ gramine.libos }}" +libos.entrypoint = "{{ entrypoint }}" + +loader.log_level = "{{ log_level }}" + +# We need `--timeout 600` to work around the problem of a constantly restarting worker process: +# the worker process is supposed to update the ctime of the shared file, and the parent process +# verifies that the worker process is alive by checking the ctime of this file every second. +# This ctime file metadata sharing between processes is currently not supported by Gramine; +# see also https://github.com/gramineproject/gramine/issues/1134. +loader.argv = ["gunicorn", "--timeout", "600", "main:app"] + +sys.enable_sigterm_injection = true + +loader.env.LD_LIBRARY_PATH = "/gramine_lib:{{ arch_libdir }}:/usr/{{ arch_libdir }}" + +fs.mounts = [ + { path = "{{ entrypoint }}", uri = "file:{{ entrypoint }}" }, + { path = "/gramine_lib", uri = "file:{{ gramine.runtimedir() }}" }, +{% for path in python.get_sys_path(python_exe_path) %} + { path = "{{ path }}", uri = "file:{{ path }}" }, +{% endfor %} + { path = "/usr/bin", uri = "file:/usr/bin" }, + { path = "{{ arch_libdir }}", uri = "file:{{ arch_libdir }}" }, + { path = "/usr/{{ arch_libdir }}", uri = "file:/usr/{{ arch_libdir }}" }, + { type = "tmpfs", path = "/tmp" }, +] + +sgx.edmm_enable = {{ 'true' if env.get('EDMM', '0') == '1' else 'false' }} +sgx.enclave_size = "512M" +sgx.max_threads = 64 + +sgx.use_exinfo = true + +sgx.trusted_files = [ + "file:{{ entrypoint }}", + "file:{{ gramine.libos }}", + "file:{{ gramine.runtimedir() }}/", +{% for path in python.get_sys_path(python_exe_path) %} + "file:{{ path }}{{ '/' if path.is_dir() else '' }}", +{% endfor %} + "file:/usr/bin/", + "file:{{ arch_libdir }}/", + "file:/usr/{{ arch_libdir }}/", + "file:main.py", +] diff --git a/gunicorn/main.py b/gunicorn/main.py new file mode 100644 index 0000000..8bc46d8 --- /dev/null +++ b/gunicorn/main.py @@ -0,0 +1,16 @@ +# Copyright (C) 2024 Gramine contributors +# SPDX-License-Identifier: BSD-3-Clause + +from flask import Flask, jsonify, request + +app = Flask(__name__) +app.config['JSONIFY_PRETTYPRINT_REGULAR'] = False + +@app.route('/hello', methods=['GET']) +def helloworld(): + if(request.method == 'GET'): + data = {"data": "Hello World"} + return jsonify(data) + +if __name__ == '__main__': + app.run(debug=True)