From e5305853366909ef08a82b842e1447793ce90f66 Mon Sep 17 00:00:00 2001 From: Lingling <55448354+linglp@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:45:13 -0400 Subject: [PATCH] feat(schematic): added nginx to schematic docker container (#2403) --- .devcontainer/devcontainer.json | 4 +- apps/schematic/api/Dockerfile | 92 +++++++++++++------ apps/schematic/api/README.md | 55 ++++++----- apps/schematic/api/certificate.conf | 28 ++++++ apps/schematic/api/debug_key_cert.py | 76 +++++++++++++++ apps/schematic/api/dhparam.pem | 13 +++ apps/schematic/api/docker-compose.yml | 4 +- apps/schematic/api/poetry.lock | 38 +++++++- apps/schematic/api/prepare-python.sh | 2 +- apps/schematic/api/prepare_key_certificate.py | 60 ++++++++++++ apps/schematic/api/pyproject.toml | 3 +- apps/schematic/api/redirect.conf | 11 +++ apps/schematic/api/save_key_certificate.py | 20 ++++ apps/schematic/api/schematic_api/main.py | 24 +++++ apps/schematic/api/self-signed.conf | 6 ++ apps/schematic/api/ssl-params.conf | 21 +++++ apps/schematic/api/uwsgi-nginx-entrypoint.sh | 53 +++++++++++ apps/schematic/api/uwsgi.ini | 13 ++- docker/schematic/services/api.yml | 2 +- 19 files changed, 459 insertions(+), 66 deletions(-) create mode 100644 apps/schematic/api/certificate.conf create mode 100644 apps/schematic/api/debug_key_cert.py create mode 100644 apps/schematic/api/dhparam.pem create mode 100644 apps/schematic/api/prepare_key_certificate.py create mode 100644 apps/schematic/api/redirect.conf create mode 100644 apps/schematic/api/save_key_certificate.py create mode 100644 apps/schematic/api/schematic_api/main.py create mode 100644 apps/schematic/api/self-signed.conf create mode 100644 apps/schematic/api/ssl-params.conf create mode 100644 apps/schematic/api/uwsgi-nginx-entrypoint.sh diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 89e24e785a..63cadc5a93 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -65,7 +65,7 @@ } }, "forwardPorts": [ - 2432, 3000, 3306, 4200, 4211, 5017, 5200, 5432, 5601, 7010, 7080, 7200, 7888, 8010, 8071, 8000, + 2432, 3000, 3306, 4200, 4211, 5017, 5200, 5432, 5601, 7010, 7443, 7200, 7888, 8010, 8071, 8000, 8080, 8081, 8082, 8083, 8084, 8085, 8086, 8090, 8091, 8092, 8200, 8787, 8888, 8889, 9090, 9104, 9200, 9411, 27017 ], @@ -114,7 +114,7 @@ "label": "schematic-api-docs", "onAutoForward": "silent" }, - "7080": { + "7443": { "label": "schematic-api", "onAutoForward": "silent" }, diff --git a/apps/schematic/api/Dockerfile b/apps/schematic/api/Dockerfile index 59f2d13676..5877b90159 100644 --- a/apps/schematic/api/Dockerfile +++ b/apps/schematic/api/Dockerfile @@ -1,41 +1,81 @@ -FROM python:3.10.12-slim-buster +FROM tiangolo/uwsgi-nginx-flask:python3.10 -ENV APP_DIR=/opt/app +# add label +LABEL org.opencontainers.image.authors='Milen Nikolov , Andrew Lamb , Mialy DeFelice , Gianna Jordan , Lingling Peng ' -SHELL ["/bin/bash", "-euxo", "pipefail", "-c"] +# the environment variables defined here are the default +# and can be overwritten by docker run -e VARIABLE = XX +# or can be overwritten by .env when using docker compose +ENV PYTHONFAULTHANDLER=1 \ + PYTHONUNBUFFERED=1 \ + PYTHONHASHSEED=random \ + PIP_NO_CACHE_DIR=off \ + PIP_DISABLE_PIP_VERSION_CHECK=on \ + PIP_DEFAULT_TIMEOUT=200 \ + POETRY_VERSION=1.3.0 \ + APP_PARENT_DIR=/app \ + NGINX_CONFIG=/etc/nginx/conf.d \ + APP_DIR=/app/app \ + ROOT=/ \ + UWSGI_INI=/app/uwsgi.ini \ + NGINX_WORKER_PROCESSES=1 \ + VERSION=$TAG -# hadolint ignore=DL3008 -RUN apt-get update -qq -y \ - && apt-get install --no-install-recommends -qq -y \ - build-essential \ - gosu \ - libpcre3 \ - libpcre3-dev \ - python3-dev \ +# run open ssl and generate certificate +RUN apt update \ && apt-get -y autoclean \ && apt-get -y autoremove \ - && rm -rf /var/lib/apt/lists/* + && rm -rf /var/lib/apt/lists/* \ + && apt-get update \ + && apt-get install -y openssl jq -WORKDIR ${APP_DIR} -COPY schematic_api schematic_api/ -COPY pyproject.toml poetry.lock uwsgi.ini ./ +# add dhparam.pem +# dhparam.pem was used in ssl-params.conf +COPY dhparam.pem /etc/ssl/dhparam.pem + +# copy all nginx config files +WORKDIR ${NGINX_CONFIG} +COPY ./self-signed.conf ./ssl-params.conf ./certificate.conf ./ + +# copy to use custom uwsgi.ini +COPY uwsgi.ini /app/uwsgi.ini + +# copy files relevant for schematic apis +COPY schematic_api pyproject.toml poetry.lock /app/app/ +# install dependencies +WORKDIR /app/app RUN pip install poetry \ && poetry config --local virtualenvs.create false \ && poetry run pip install "cython<3.0.0" \ && poetry run pip install --no-build-isolation pyyaml==5.4.1 \ - && poetry install --with prod --no-root --no-interaction --no-ansi \ - && pip cache purge + && poetry install --with prod --no-root --no-interaction --no-ansi -WORKDIR / -COPY docker-entrypoint.sh ./ -RUN chmod +x docker-entrypoint.sh +# Update file permission +RUN mkdir /root/.synapseCache /app/app/manifests -EXPOSE 7080 +# temporary here to ensure .synapseCache is not empty +RUN echo "This is a test file." > /root/.synapseCache/test.txt +# temporary here until we move .synapseCache to a different path +RUN chmod -R 777 /root /app + +# Modify entrypoint script to allow SSL private key and certificate to be saved +WORKDIR ${ROOT} +COPY ./uwsgi-nginx-entrypoint.sh ./entrypoint2.sh +COPY ./uwsgi-nginx-entrypoint.sh ./uwsgi-nginx-entrypoint2.sh +COPY ./save_key_certificate.py ./save_key_certificate.py + +RUN chmod +x uwsgi-nginx-entrypoint2.sh +RUN chmod +x entrypoint2.sh +RUN chown -R nginx /uwsgi-nginx-entrypoint2.sh +RUN chown -R nginx /entrypoint2.sh + +WORKDIR ${APP_DIR} -ENTRYPOINT ["/docker-entrypoint.sh"] +# specify entrypoint again to generate config +# have to respecify CMD too +ENTRYPOINT ["/entrypoint2.sh"] +CMD ["/start.sh"] -# Run server in development mode -# CMD ["python", "-m", "openapi_server"] -# Run server in production mode -CMD ["uwsgi", "--ini", "uwsgi.ini", "--lazy", "--http", ":7080"] \ No newline at end of file +# Expose ports +EXPOSE 7443 \ No newline at end of file diff --git a/apps/schematic/api/README.md b/apps/schematic/api/README.md index e3f8116c2b..9aad77535c 100644 --- a/apps/schematic/api/README.md +++ b/apps/schematic/api/README.md @@ -1,39 +1,24 @@ -# OpenAPI generated server - -## Overview -This server was generated by the [OpenAPI Generator](https://openapi-generator.tech) project. By using the -[OpenAPI-Spec](https://openapis.org) from a remote server, you can easily generate a server stub. This -is an example of building a OpenAPI-enabled Flask server. - -This example uses the [Connexion](https://github.com/zalando/connexion) library on top of Flask. - -## Requirements -Python 3.5.2+ - -## Usage -To run the server, please execute the following from the root directory: +# Run Schematic APIs +## Running without Docker +To run the server, please execute the following from folder `apps/schematic/api`: ``` -pip3 install -r requirements.txt -python3 -m schematic_api +poetry shell ``` - -and open your browser to here: +To install dependencies: ``` -http://localhost:8080/v1/ui/ +poetry install ``` - -Your OpenAPI definition lives here: +And run schematic APIs: ``` -http://localhost:8080/v1/openapi.json +python3 -m schematic-api ``` +and open your browser to here: -To launch the integration tests, use tox: ``` -sudo pip install tox -tox +http://127.0.0.1:7080/api/v1/ui/ ``` ## Running with Docker @@ -41,9 +26,21 @@ tox To run the server on a Docker container, please execute the following from the root directory: ```bash +# Prepare the development environment of the project with nx prepare schematic-api. This will create a venv and install all the Python dependencies. +nx prepare schematic-api + +# You only need to run this command one time +# This step adds SSL private key and certificate as environment variable in .env file +python3 apps/schematic/api/prepare_key_certificate.py + # building the image -docker build -t schematic_api . +nx build-image schematic-api -# starting up a container -docker run -p 8080:8080 schematic_api -``` \ No newline at end of file +# Start the containerized REST API with: +nx serve-detach schematic-api +``` +You could open your browser here: +``` +https://localhost:7443/api/v1/ui/ +``` +Note: When the OpenAPI description has changed, regenerate the REST API with nx run schematic-api:generate. Also, `dhparam.pem` was generated by using command: `RUN openssl dhparam -out dhparam.pem 4096`. If there's an issue with the `dhparam.pem` in the future, please re run the command. \ No newline at end of file diff --git a/apps/schematic/api/certificate.conf b/apps/schematic/api/certificate.conf new file mode 100644 index 0000000000..805572fbf6 --- /dev/null +++ b/apps/schematic/api/certificate.conf @@ -0,0 +1,28 @@ +server { + # listen to port 80 for http requests + listen 80 http2 default_server; + # listen to port 7443 for https requests + listen 7443 ssl http2 default_server; + listen [::]:7443 ssl http2 default_server; + include /etc/nginx/conf.d/self-signed.conf; + include /etc/nginx/conf.d/ssl-params.conf; + server_name 127.0.0.1; + proxy_read_timeout 300; + proxy_connect_timeout 300; + proxy_send_timeout 300; + error_page 497 https://$http_host$request_uri; + location / { + try_files $uri @app; + } + location @app { + include uwsgi_params; + uwsgi_pass unix:///tmp/uwsgi.sock; + } + location /static { + alias /app/static; + } + location /health { + return 200 'alive'; + add_header Content-Type text/plain; + } +} \ No newline at end of file diff --git a/apps/schematic/api/debug_key_cert.py b/apps/schematic/api/debug_key_cert.py new file mode 100644 index 0000000000..1425b422b7 --- /dev/null +++ b/apps/schematic/api/debug_key_cert.py @@ -0,0 +1,76 @@ +from environs import Env +import subprocess +import json +import base64 +import os + +# Create an instance of Env +env = Env() + +# Load the .env file +env.read_env(path=".env") + +# Define key and certificate file path +test_private_key_file_path = "test_private_key.key" +test_certificate_key_file_path = "test_certificate.crt" + +# Access the variables +secret_manager_secrets = os.environ["SECRETS_MANAGER_SECRETS"] + +ssl_private_key = json.loads(secret_manager_secrets)["SSL_PRIVATE_KEY"] +ssl_certificate_key = json.loads(secret_manager_secrets)["SSL_CERTIFICATE"] + +# delete preivous results if necessary +if os.path.exists(test_private_key_file_path): + os.remove(test_private_key_file_path) +if os.path.exists(test_certificate_key_file_path): + os.remove(test_certificate_key_file_path) + +# make sure that key and certificate can be decoded in correct format +with open(test_private_key_file_path, "wb") as file: + decoded_private_key = base64.b64decode(ssl_private_key) + file.write(decoded_private_key) + +with open(test_certificate_key_file_path, "wb") as file: + decoded_ssl_certificate_key = base64.b64decode(ssl_certificate_key) + file.write(decoded_ssl_certificate_key) + + +# Make sure that certificate and key match each other +def get_md5_cert(file): + openssl_x509_command = ["openssl", "x509", "-noout", "-modulus", "-in", file] + openssl_md5_command = ["openssl", "md5"] + + x509_process = subprocess.Popen(openssl_x509_command, stdout=subprocess.PIPE) + md5_process = subprocess.Popen( + openssl_md5_command, stdin=x509_process.stdout, stdout=subprocess.PIPE + ) + + output, error = md5_process.communicate() + + if error: + print("error getting md5", error.decode("utf-8")) + + return output.decode("utf-8").strip() + + +def get_md5_private_key(file): + openssl_rsa_command = ["openssl", "rsa", "-noout", "-modulus", "-in", file] + openssl_md5_command = ["openssl", "md5"] + + rsa_process = subprocess.Popen(openssl_rsa_command, stdout=subprocess.PIPE) + md5_process = subprocess.Popen( + openssl_md5_command, stdin=rsa_process.stdout, stdout=subprocess.PIPE + ) + + output, error = md5_process.communicate() + + if error: + print(error.decode("utf-8")) + return output.decode("utf-8").strip() + + +md5_key = get_md5_private_key(test_private_key_file_path) +md5_cert = get_md5_cert(test_certificate_key_file_path) + +assert md5_key == md5_cert diff --git a/apps/schematic/api/dhparam.pem b/apps/schematic/api/dhparam.pem new file mode 100644 index 0000000000..71901dec86 --- /dev/null +++ b/apps/schematic/api/dhparam.pem @@ -0,0 +1,13 @@ +-----BEGIN DH PARAMETERS----- +MIICCAKCAgEAwzcUARDhjqW74+OsC8quGIJhkuL60o9GHuR3feEOky1cY1D/Qm0I +s32FFsm5PutkJ6ZUFf86Uae+ARrQlsidsryHF+bHZpIBe12h/4pcQhH9ghdmHcq6 +lqJiD55Gb+uzqe/rmOCInEjl7WXxLvYDzndRbP9gY7I5tVQCdfMTYm7ZYK+Xt53X +ufIkwwkxj3qXimFIEeTJEqK837u7VM9Q5H+nZR+W/lAw8IvYp3wH3IrOmBflWBi/ +AaHCemd+gwaS8nZFrNSWffOd9Gg+tuFehlnCSMb4FudEbLk+AyvDAq1RMI1bH1SO ++go/i/iX3u616eXi83/U7JUMNj655Iyoc5F02GlDjyvRRauV50S4nIB7t/mxgGEZ +B7C1wce23PwhsRLxsT5xlti7T3QWgvO0w/P+jnCvwfyu9jUzP87qozAYAV8jCMzx +Henya27o3Qewhr6IuMm7tqo5Bz28AJMm+/DL+XQfF4ceP4XzA4OJVKfQzDKXh4PI +BSw8qY85esWJ3yQjwITygdOMHIxRVQA7Et7kKee3D3iDKeHdRu1m4hLtqRbHesrc +QMdhbZBW6WwYQPeqDhkYKBFbAFrrQHkNAOsduq6/OpWoOuu4yL4K4Sfkd8wQ/3At +e6lfZimHDEHlCIfKM3+MS91zkeOHlBVFVI+H2LiBk6tVcPm1hSK9c+sCAQI= +-----END DH PARAMETERS----- diff --git a/apps/schematic/api/docker-compose.yml b/apps/schematic/api/docker-compose.yml index fab337eb92..076b8db28a 100644 --- a/apps/schematic/api/docker-compose.yml +++ b/apps/schematic/api/docker-compose.yml @@ -1,4 +1,4 @@ -version: "3.8" +version: "3.10" services: schematic-api: @@ -13,7 +13,7 @@ services: networks: - schematic ports: - - "${SERVER_PORT}:7080" + - "7443:7443" # volumes: # openchallenges-mariadb: diff --git a/apps/schematic/api/poetry.lock b/apps/schematic/api/poetry.lock index 09d2a91ee5..cc770acba5 100644 --- a/apps/schematic/api/poetry.lock +++ b/apps/schematic/api/poetry.lock @@ -882,6 +882,26 @@ files = [ {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, ] +[[package]] +name = "environs" +version = "11.0.0" +description = "simplified environment variable parsing" +optional = false +python-versions = ">=3.8" +files = [ + {file = "environs-11.0.0-py3-none-any.whl", hash = "sha256:e0bcfd41c718c07a7db422f9109e490746450da38793fe4ee197f397b9343435"}, + {file = "environs-11.0.0.tar.gz", hash = "sha256:069727a8f73d8ba8d033d3cd95c0da231d44f38f1da773bf076cef168d312ee8"}, +] + +[package.dependencies] +marshmallow = ">=3.13.0" +python-dotenv = "*" + +[package.extras] +dev = ["environs[tests]", "pre-commit (>=3.5,<4.0)", "tox"] +django = ["dj-database-url", "dj-email-url", "django-cache-url"] +tests = ["environs[django]", "pytest"] + [[package]] name = "et-xmlfile" version = "1.1.0" @@ -3054,6 +3074,20 @@ files = [ [package.dependencies] six = ">=1.5" +[[package]] +name = "python-dotenv" +version = "1.0.1" +description = "Read key-value pairs from a .env file and set them as environment variables" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"}, + {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"}, +] + +[package.extras] +cli = ["click (>=5.0)"] + [[package]] name = "python-json-logger" version = "2.0.7" @@ -4539,5 +4573,5 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p [metadata] lock-version = "2.0" -python-versions = "3.10.12" -content-hash = "52634af3e72383ecb08b2a8001f8e9272665cc019c990726989a27e42e4c68da" +python-versions = "3.10.13" +content-hash = "6363b8509c424e4d21e1529d5a8115101f3cbc03b5790240b683172e7da48b64" diff --git a/apps/schematic/api/prepare-python.sh b/apps/schematic/api/prepare-python.sh index 4ab7607475..ee493c5094 100755 --- a/apps/schematic/api/prepare-python.sh +++ b/apps/schematic/api/prepare-python.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash -PYTHON_VERSION="3.10.12" +PYTHON_VERSION="3.10.13" pyenv install --skip-existing $PYTHON_VERSION diff --git a/apps/schematic/api/prepare_key_certificate.py b/apps/schematic/api/prepare_key_certificate.py new file mode 100644 index 0000000000..316a4e53c1 --- /dev/null +++ b/apps/schematic/api/prepare_key_certificate.py @@ -0,0 +1,60 @@ +import base64 +import json +import subprocess + + +# Define the paths to your SSL certificate and key +cert_file_path = "private_localhost_certificate.crt" +key_file_path = "private_localhost.key" +env_file_path = ".env" # Path to your .env file + +# Define the OpenSSL command +openssl_command = [ + "openssl", + "req", + "-x509", + "-nodes", + "-days", + "365", + "-subj", + "/C=US/ST=WA/O=SAGE", + "-newkey", + "rsa:2048", + "-keyout", + key_file_path, + "-out", + cert_file_path, +] + +# Run the OpenSSL command +try: + subprocess.run(openssl_command, check=True) + print("SSL certificate and key generated successfully.") +except subprocess.CalledProcessError as e: + print(f"Error generating SSL certificate and key: {e}") + + +# Function to read a file and encode its contents to Base64 +# Certificate has to be in base64 format otherwise can't be parse properly as environment variables +def encode_file_to_base64(file_path): + with open(file_path, "rb") as file: + return base64.b64encode(file.read()).decode("utf-8") + + +# Encode the SSL certificate and key +ssl_certificate_base64 = encode_file_to_base64(cert_file_path) +ssl_private_key_base64 = encode_file_to_base64(key_file_path) + +# Combine into a JSON object +ssl_config_json = json.dumps( + { + "SSL_CERTIFICATE": ssl_certificate_base64, + "SSL_PRIVATE_KEY": ssl_private_key_base64, + } +) + +# Append to .env file +with open(env_file_path, "a") as env_file: + env_file.write(f"SECRETS_MANAGER_SECRETS={ssl_config_json}\n") + +print("SSL certificate and key have been encoded and appended to the .env file.") diff --git a/apps/schematic/api/pyproject.toml b/apps/schematic/api/pyproject.toml index c6bb95ac11..0816948c4b 100644 --- a/apps/schematic/api/pyproject.toml +++ b/apps/schematic/api/pyproject.toml @@ -7,7 +7,7 @@ readme = "README.md" packages = [{include = "schematic_api"}] [tool.poetry.dependencies] -python = "3.10.12" +python = "3.10.13" connexion = {version = "2.14.1", extras = ["swagger-ui"]} swagger-ui-bundle = "0.0.9" python-dateutil = "2.8.2" @@ -18,6 +18,7 @@ schematicpy = "24.2.1" # This is to avoid deprecationWarnings # See https://sagebionetworks.jira.com/browse/FDS-1324 jsonschema = ">=4.0.0 <4.10.0" +environs = "^11.0.0" [tool.poetry.group.dev.dependencies] black = "23.7.0" diff --git a/apps/schematic/api/redirect.conf b/apps/schematic/api/redirect.conf new file mode 100644 index 0000000000..5a0871b3cc --- /dev/null +++ b/apps/schematic/api/redirect.conf @@ -0,0 +1,11 @@ +server { + # listen to port 443 for https requests + listen 443 ssl http2 default_server; + listen [::]:443 ssl http2 default_server; + include /etc/nginx/conf.d/self-signed.conf; + include /etc/nginx/conf.d/ssl-params.conf; + server_name 127.0.0.1; + + # Redirect the browser to port 7443 + return 301 https://$server_name:7443$request_uri; +} \ No newline at end of file diff --git a/apps/schematic/api/save_key_certificate.py b/apps/schematic/api/save_key_certificate.py new file mode 100644 index 0000000000..1b88809362 --- /dev/null +++ b/apps/schematic/api/save_key_certificate.py @@ -0,0 +1,20 @@ +import os +import base64 +import json + +secret_manager_secrets = os.environ["SECRETS_MANAGER_SECRETS"] + +ssl_private_key = json.loads(secret_manager_secrets)["SSL_PRIVATE_KEY"] +ssl_certificate_key = json.loads(secret_manager_secrets)["SSL_CERTIFICATE"] + +# save the key and certificate as files +test_private_key_file_path = "/etc/ssl/private/localhost.key" +test_certificate_key_file_path = "/etc/ssl/certs/localhost.crt" + +with open(test_private_key_file_path, "wb") as file: + decoded_private_key = base64.b64decode(ssl_private_key) + file.write(decoded_private_key) + +with open(test_certificate_key_file_path, "wb") as file: + decoded_ssl_certificate_key = base64.b64decode(ssl_certificate_key) + file.write(decoded_ssl_certificate_key) diff --git a/apps/schematic/api/schematic_api/main.py b/apps/schematic/api/schematic_api/main.py new file mode 100644 index 0000000000..7666a5d5b1 --- /dev/null +++ b/apps/schematic/api/schematic_api/main.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +import connexion +from flask_cors import CORS +import flask +from schematic_api import encoder + +app = connexion.App(__name__, specification_dir="./openapi/") +app.app.json_encoder = encoder.JSONEncoder +app.add_api( + "openapi.yaml", arguments={"title": "Schematic REST API"}, pythonic_params=True +) +app.add_url_rule("/", "ui", lambda: flask.redirect("/api/v1/ui")) + +# add CORS support +# https://connexion.readthedocs.io/en/latest/cookbook.html#cors-support +CORS(app.app, resources={r"/api/*": {"origins": "*"}}) + + +def main(): + app.run(port=80, debug=False) + + +if __name__ == "__main__": + main() diff --git a/apps/schematic/api/self-signed.conf b/apps/schematic/api/self-signed.conf new file mode 100644 index 0000000000..0ea774915b --- /dev/null +++ b/apps/schematic/api/self-signed.conf @@ -0,0 +1,6 @@ +# The ultimate goal is to set up a self-signed SSL certificate for Nginx to use +# A self-signed certificate is required to encrypt the communication between flask API server and any clients +# This configuration file is needed to configure nginx to use SSL certificates + +ssl_certificate /etc/ssl/certs/localhost.crt; +ssl_certificate_key /etc/ssl/private/localhost.key; \ No newline at end of file diff --git a/apps/schematic/api/ssl-params.conf b/apps/schematic/api/ssl-params.conf new file mode 100644 index 0000000000..cec80f7ed4 --- /dev/null +++ b/apps/schematic/api/ssl-params.conf @@ -0,0 +1,21 @@ +# The configuration file was borrowed from: https://www.digitalocean.com/community/tutorials/how-to-create-a-self-signed-ssl-certificate-for-nginx-in-ubuntu-22-04 +# This configuration file is needed for setting up nginx securely. + +ssl_protocols TLSv1.2; +ssl_prefer_server_ciphers on; +ssl_dhparam /etc/ssl/dhparam.pem; +ssl_ciphers ECDHE-RSA-AES256-GCM-SHA512:DHE-RSA-AES256-GCM-SHA512:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-SHA384; +ssl_ecdh_curve secp384r1; # Requires nginx >= 1.1.0 +ssl_session_timeout 10m; +ssl_session_cache shared:SSL:10m; +ssl_session_tickets off; # Requires nginx >= 1.5.9 +ssl_stapling on; # Requires nginx >= 1.3.7 +ssl_stapling_verify on; # Requires nginx => 1.3.7 +resolver 8.8.8.8 8.8.4.4 valid=300s; +resolver_timeout 5s; +# Disable strict transport security for now. You can uncomment the following +# line if you understand the implications. +# add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload"; +add_header X-Frame-Options DENY; +add_header X-Content-Type-Options nosniff; +add_header X-XSS-Protection "1; mode=block"; \ No newline at end of file diff --git a/apps/schematic/api/uwsgi-nginx-entrypoint.sh b/apps/schematic/api/uwsgi-nginx-entrypoint.sh new file mode 100644 index 0000000000..8b42bf1016 --- /dev/null +++ b/apps/schematic/api/uwsgi-nginx-entrypoint.sh @@ -0,0 +1,53 @@ +#! /usr/bin/env sh +set -e +/uwsgi-nginx-entrypoint.sh + +# save private key and certificate from environment variable +if [ -n "$SECRETS_MANAGER_SECRETS" ]; then + python3 /save_key_certificate.py + + # Ensure the private key file is securely accessible + chmod 600 /etc/ssl/private/localhost.key +fi + +# Get the URL for static files from the environment variable +USE_STATIC_URL=${STATIC_URL:-'/static'} +# Get the absolute path of the static files from the environment variable +USE_STATIC_PATH=${STATIC_PATH:-'/app/static'} +# Get the listen port for Nginx, default to 80 +USE_LISTEN_PORT=${LISTEN_PORT:-80} + +if [ -f /app/nginx.conf ]; then + cp /app/nginx.conf /etc/nginx/nginx.conf +else + content_server='server {\n' + content_server=$content_server" listen ${USE_LISTEN_PORT};\n" + content_server=$content_server' location / {\n' + content_server=$content_server' try_files $uri @app;\n' + content_server=$content_server' }\n' + content_server=$content_server' location @app {\n' + content_server=$content_server' include uwsgi_params;\n' + content_server=$content_server' uwsgi_pass unix:///tmp/uwsgi.sock;\n' + content_server=$content_server' }\n' + content_server=$content_server" location $USE_STATIC_URL {\n" + content_server=$content_server" alias $USE_STATIC_PATH;\n" + content_server=$content_server' }\n' + # If STATIC_INDEX is 1, serve / with /static/index.html directly (or the static URL configured) + if [ "$STATIC_INDEX" = 1 ] ; then + content_server=$content_server' location = / {\n' + content_server=$content_server" index $USE_STATIC_URL/index.html;\n" + content_server=$content_server' }\n' + fi + content_server=$content_server'}\n' + # Save generated server /etc/nginx/conf.d/nginx.conf + printf "$content_server" > /etc/nginx/conf.d/nginx.conf +fi + +# For Alpine: +# Explicitly add installed Python packages and uWSGI Python packages to PYTHONPATH +# Otherwise uWSGI can't import Flask +if [ -n "$ALPINEPYTHON" ] ; then + export PYTHONPATH=$PYTHONPATH:/usr/local/lib/$ALPINEPYTHON/site-packages:/usr/lib/$ALPINEPYTHON/site-packages +fi + +exec "$@" \ No newline at end of file diff --git a/apps/schematic/api/uwsgi.ini b/apps/schematic/api/uwsgi.ini index 5db4087341..434cf70cfb 100644 --- a/apps/schematic/api/uwsgi.ini +++ b/apps/schematic/api/uwsgi.ini @@ -1,7 +1,7 @@ [uwsgi] # The variables http and *-socket are passed as command line arguments and # must not be specified in this file. -wsgi-file = schematic_api/__main__.py +wsgi-file = schematic_api/main.py callable = app uid = www-data gid = www-data @@ -12,4 +12,13 @@ chmod-sock = 660 vacuum = true die-on-term = true thunder-lock = true -http-keepalive = true \ No newline at end of file +http-keepalive = true +harakiri-verbose = true +http-timeout = 300 # necessary for preventing time-out +uwsgi_read_timeout = 300 # necessary for preventing time-out +uwsgi_send_timeout = 300 # necessary for preventing time-out +buffer-size = 32768 # for dealing with long token in DCA and DFA +# for dealing with OSError: write error +ignore-sigpipe=true +ignore-write-errors=true +disable-write-exception=true \ No newline at end of file diff --git a/docker/schematic/services/api.yml b/docker/schematic/services/api.yml index b2896019c4..b00b23d56f 100644 --- a/docker/schematic/services/api.yml +++ b/docker/schematic/services/api.yml @@ -10,7 +10,7 @@ services: networks: - schematic ports: - - '7080:7080' + - '7443:7443' # depends_on: # openchallenges-config-server: # condition: service_healthy