From 8cec76f0050787d2aa8bf422cda007b648b24b0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20Gonz=C3=A1lez?= Date: Tue, 4 Nov 2025 09:08:14 -0400 Subject: [PATCH 1/4] chore: drop support for Python 3.9 after EOF --- .github/workflows/ci.yml | 2 +- setup.py | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 090344a..151b25c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ['3.9', '3.12'] + python-version: ['3.10', '3.12'] steps: - uses: actions/checkout@v4 diff --git a/setup.py b/setup.py index 980b832..6cf1cd4 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,8 @@ def load_about(): long_description=load_readme(), packages=find_packages(exclude=["tests*"]), include_package_data=True, - python_requires=">=3.8", install_requires=["tutor>=20.0.0,<21"], + python_requires=">=3.10", entry_points={"tutor.plugin.v1": ["codejail = tutorcodejail.plugin"]}, classifiers=[ "Development Status :: 3 - Alpha", @@ -52,8 +52,6 @@ def load_about(): "License :: OSI Approved :: GNU Affero General Public License v3", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", From 41cec7c7d0d228f72614850587632840375b5c15 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20Gonz=C3=A1lez?= Date: Tue, 4 Nov 2025 09:09:01 -0400 Subject: [PATCH 2/4] feat!: support for the ulmo release --- setup.py | 2 +- tutorcodejail/__about__.py | 2 +- tutorcodejail/patches/k8s-deployments | 10 +++++--- tutorcodejail/plugin.py | 4 +-- .../codejail/build/codejail/Dockerfile | 25 ++++++++++--------- 5 files changed, 23 insertions(+), 20 deletions(-) diff --git a/setup.py b/setup.py index 6cf1cd4..c7ff95f 100644 --- a/setup.py +++ b/setup.py @@ -43,8 +43,8 @@ def load_about(): long_description=load_readme(), packages=find_packages(exclude=["tests*"]), include_package_data=True, - install_requires=["tutor>=20.0.0,<21"], python_requires=">=3.10", + install_requires=["tutor~=21.0"], entry_points={"tutor.plugin.v1": ["codejail = tutorcodejail.plugin"]}, classifiers=[ "Development Status :: 3 - Alpha", diff --git a/tutorcodejail/__about__.py b/tutorcodejail/__about__.py index 1536159..f9c7ad1 100644 --- a/tutorcodejail/__about__.py +++ b/tutorcodejail/__about__.py @@ -1,2 +1,2 @@ """Helps you keep your cool when creating dozens of open edX and eduNEXT environments.""" -__version__ = "20.1.0" +__version__ = "21.0.0" diff --git a/tutorcodejail/patches/k8s-deployments b/tutorcodejail/patches/k8s-deployments index 6e36a93..59f19c2 100644 --- a/tutorcodejail/patches/k8s-deployments +++ b/tutorcodejail/patches/k8s-deployments @@ -13,11 +13,13 @@ spec: metadata: labels: app.kubernetes.io/name: codejailservice - annotations: - {% if CODEJAIL_ENFORCE_APPARMOR %} - container.apparmor.security.beta.kubernetes.io/codejailservice: "localhost/docker-edx-sandbox" - {% endif %} spec: + {% if CODEJAIL_ENFORCE_APPARMOR %} + securityContext: + appArmorProfile: + type: Localhost + localhostProfile: docker-edx-sandbox + {% endif %} containers: - name: codejailservice image: {{ CODEJAIL_DOCKER_IMAGE }} diff --git a/tutorcodejail/plugin.py b/tutorcodejail/plugin.py index 8c9bac5..e3fd24e 100644 --- a/tutorcodejail/plugin.py +++ b/tutorcodejail/plugin.py @@ -23,9 +23,9 @@ "ENFORCE_APPARMOR": True, "EXTRA_PIP_REQUIREMENTS": [], "HOST": "codejailservice", - "SANDBOX_PYTHON_VERSION": "3.11.9", + "SANDBOX_PYTHON_VERSION": "3.11.14", "SERVICE_REPOSITORY": "https://github.com/edunext/codejailservice.git", - "SERVICE_VERSION": "release/teak.1", + "SERVICE_VERSION": "{{ OPENEDX_COMMON_VERSION }}", "SKIP_INIT": False, "VERSION": __version__, }, diff --git a/tutorcodejail/templates/codejail/build/codejail/Dockerfile b/tutorcodejail/templates/codejail/build/codejail/Dockerfile index 2f584cf..40d6a2c 100644 --- a/tutorcodejail/templates/codejail/build/codejail/Dockerfile +++ b/tutorcodejail/templates/codejail/build/codejail/Dockerfile @@ -14,9 +14,9 @@ RUN apt update && \ libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \ xz-utils tk-dev libffi-dev liblzma-dev python3-openssl git subversion ENV PYENV_ROOT=/opt/pyenv -RUN git clone https://github.com/pyenv/pyenv $PYENV_ROOT --branch v2.4.0 --depth 1 +RUN git clone https://github.com/pyenv/pyenv $PYENV_ROOT --branch v2.6.12 --depth 1 -ARG CODEJAILSERVICE_PYTHON_VERSION=3.11.9 +ARG CODEJAILSERVICE_PYTHON_VERSION=3.11.14 RUN $PYENV_ROOT/bin/pyenv install $CODEJAILSERVICE_PYTHON_VERSION ARG SANDBOX_PYTHON_VERSION="{{ CODEJAIL_SANDBOX_PYTHON_VERSION }}" @@ -56,7 +56,7 @@ WORKDIR /var/tmp RUN mkdir -p common/lib/ ADD $EDX_PLATFORM_REPOSITORY#$EDX_PLATFORM_VERSION:requirements/edx-sandbox/ . -RUN pip3 install -r releases/teak.txt +RUN pip3 install -r base.txt {% if CODEJAIL_EXTRA_PIP_REQUIREMENTS %} # Allows you to add extra pip requirements to your codejail sandbox. @@ -85,15 +85,16 @@ RUN groupadd -r sandbox && useradd -m -r -g sandbox sandbox && chown -R sandbox: ENV PATH=/openedx/venv/bin:${PATH} ENV VIRTUAL_ENV=/openedx/venv/ +ENV UWSGI_WORKERS=2 WORKDIR /openedx/codejailservice EXPOSE 8550 -CMD uwsgi \ - --http 0.0.0.0:8550 \ - --thunder-lock \ - --single-interpreter \ - --enable-threads \ - --processes=${UWSGI_WORKERS:-2} \ - --buffer-size=8192 \ - --max-requests=1000 \ - --wsgi-file /openedx/codejailservice/wsgi.py + +CMD [ "uwsgi", \ + "--http=0.0.0.0:8550", \ + "--thunder-lock", \ + "--single-interpreter", \ + "--enable-threads", \ + "--buffer-size=8192", \ + "--max-requests=1000", \ + "--wsgi-file=/openedx/codejailservice/wsgi.py" ] From b200d7a729bb3d9386875f4050d04fe0d3fd404d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mois=C3=A9s=20Gonz=C3=A1lez?= Date: Fri, 21 Nov 2025 17:55:37 -0400 Subject: [PATCH 3/4] feat: support the openedx/codejail-service implementation --- README.rst | 15 +- tutorcodejail/patches/k8s-deployments | 38 ++++ tutorcodejail/patches/k8s-services | 2 + .../patches/kustomization-configmapgenerator | 9 +- .../local-docker-compose-jobs-services | 9 + .../patches/local-docker-compose-services | 19 +- tutorcodejail/plugin.py | 121 ++++++------ .../apps/codejail-service-v2/tutor.py | 34 ++++ .../apps/{config => codejail}/tutor.py | 0 ...edx-sandbox => docker-edx-sandbox.profile} | 0 .../profiles/openedx-codejail-service.profile | 132 +++++++++++++ .../build/codejail-service/Dockerfile | 173 ++++++++++++++++++ .../build/codejail_apparmor/Dockerfile | 10 +- 13 files changed, 497 insertions(+), 65 deletions(-) create mode 100644 tutorcodejail/patches/local-docker-compose-jobs-services create mode 100644 tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py rename tutorcodejail/templates/codejail/apps/{config => codejail}/tutor.py (100%) rename tutorcodejail/templates/codejail/apps/profiles/{docker-edx-sandbox => docker-edx-sandbox.profile} (100%) create mode 100644 tutorcodejail/templates/codejail/apps/profiles/openedx-codejail-service.profile create mode 100644 tutorcodejail/templates/codejail/build/codejail-service/Dockerfile diff --git a/README.rst b/README.rst index 51ddcc0..e329eb1 100644 --- a/README.rst +++ b/README.rst @@ -6,6 +6,7 @@ secure execution of untrusted code within sandboxes, providing a safe environmen .. _Tutor: https://docs.tutor.overhang.io .. _Codejail Service: https://github.com/eduNEXT/codejailservice +.. _Codejail Service V2: https://github.com/openedx/codejail-service .. _Codejail: https://github.com/openedx/codejail Installation @@ -23,9 +24,9 @@ You can install a specific version by adding the tag, branch, or commit: .. code-block:: bash - pip install tutor-contrib-codejail==v20.0.0 + pip install tutor-contrib-codejail~=21.0 # or install from the source - pip install git+https://github.com/edunext/tutor-contrib-codejail@v20.0.0 + pip install git+https://github.com/edunext/tutor-contrib-codejail@v21.0.0 Usage ----- @@ -55,14 +56,20 @@ Configuration To customize the configuration, update the following settings in Tutor: - ``CODEJAIL_APPARMOR_DOCKER_IMAGE``: (default: ``docker.io/ednxops/codejail_apparmor_loader:latest``) +- ``CODEJAIL_DOCKER_IMAGE_V2`` : (default: ``{{ CODEJAIL_DOCKER_IMAGE }}-v2``) - ``CODEJAIL_DOCKER_IMAGE``: (default: ``docker.io/ednxops/codejailservice:{{__version__}}``) - ``CODEJAIL_ENABLE_K8S_DAEMONSET`` (default: ``False``) - ``CODEJAIL_ENFORCE_APPARMOR`` (default: ``True``) - ``CODEJAIL_EXTRA_PIP_REQUIREMENTS`` (default: ``[]``) - ``CODEJAIL_SANDBOX_PYTHON_VERSION`` (default: ``3.11.9``) -- ``CODEJAIL_SERVICE_REPOSITORY`` (default ``https://github.com/edunext/codejailservice.git```) -- ``CODEJAIL_SERVICE_VERSION`` (default: ``release/teak.1``), +- ``CODEJAIL_SERVICE_REPOSITORY`` (default: ``https://github.com/edunext/codejailservice.git```) +- ``CODEJAIL_SERVICE_VERSION`` (default: ``{{ OPENEDX_COMMON_VERSION }}``), - ``CODEJAIL_SKIP_INIT`` (default: ``False``) +- ``SERVICE_V2_REPOSITORY``: (default: ``https://github.com/openedx/codejail-service.git``) +- ``SERVICE_V2_VERSION``: (default: ``{{ OPENEDX_COMMON_VERSION }}``) +- ``USE_SERVICE_V2``: (default: ``False``) + +The ``CODEJAIL_V2_*`` settings are meant to be used only during the Ulmo release a Custom Image ~~~~~~~~~~~~ diff --git a/tutorcodejail/patches/k8s-deployments b/tutorcodejail/patches/k8s-deployments index 59f19c2..f22d9e5 100644 --- a/tutorcodejail/patches/k8s-deployments +++ b/tutorcodejail/patches/k8s-deployments @@ -1,4 +1,41 @@ --- +{% if CODEJAIL_USE_SERVICE_V2 %} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: codejailservice + labels: + app.kubernetes.io/name: codejailservice +spec: + selector: + matchLabels: + app.kubernetes.io/name: codejailservice + template: + metadata: + labels: + app.kubernetes.io/name: codejailservice + spec: + securityContext: + appArmorProfile: + type: Localhost + localhostProfile: openedx_codejail_service + containers: + - name: codejailservice + image: {{ CODEJAIL_DOCKER_IMAGE_V2 }} + ports: + - containerPort: 8550 + env: + - name: DJANGO_SETTINGS_MODULE + value: codejail_service.settings.tutor + volumeMounts: + - mountPath: /app/codejail_service/settings/tutor.py + name: settings-codejail + subPath: tutor.py + volumes: + - name: settings-codejail + configMap: + name: settings-codejail +{% else %} apiVersion: apps/v1 kind: Deployment metadata: @@ -36,6 +73,7 @@ spec: - name: settings-codejail configMap: name: settings-codejail +{% endif %} {% if CODEJAIL_ENABLE_K8S_DAEMONSET %} --- apiVersion: apps/v1 diff --git a/tutorcodejail/patches/k8s-services b/tutorcodejail/patches/k8s-services index 60b6f3f..7b259c1 100644 --- a/tutorcodejail/patches/k8s-services +++ b/tutorcodejail/patches/k8s-services @@ -3,6 +3,8 @@ apiVersion: v1 kind: Service metadata: name: codejailservice + labels: + app.kubernetes.io/name: codejailservice spec: type: ClusterIP ports: diff --git a/tutorcodejail/patches/kustomization-configmapgenerator b/tutorcodejail/patches/kustomization-configmapgenerator index ea27483..e1adba7 100644 --- a/tutorcodejail/patches/kustomization-configmapgenerator +++ b/tutorcodejail/patches/kustomization-configmapgenerator @@ -1,12 +1,17 @@ - name: codejail-profile files: - - plugins/codejail/apps/profiles/docker-edx-sandbox + - plugins/codejail/apps/profiles/docker-edx-sandbox.profile + - plugins/codejail/apps/profiles/openedx-codejail-service.profile options: labels: app.kubernetes.io/name: codejail-aa-loader - name: settings-codejail files: - - plugins/codejail/apps/config/tutor.py + {% if CODEJAIL_USE_SERVICE_V2 %} + - plugins/codejail/apps/codejail-service-v2/tutor.py + {% else %} + - plugins/codejail/apps/codejail/tutor.py + {% endif %} options: labels: app.kubernetes.io/name: codejailservice diff --git a/tutorcodejail/patches/local-docker-compose-jobs-services b/tutorcodejail/patches/local-docker-compose-jobs-services new file mode 100644 index 0000000..030b736 --- /dev/null +++ b/tutorcodejail/patches/local-docker-compose-jobs-services @@ -0,0 +1,9 @@ +codejail-apparmor-job: + image: {{ CODEJAIL_APPARMOR_DOCKER_IMAGE }} + privileged: true + environment: + SKIP_INIT: "{{ CODEJAIL_SKIP_INIT }}" + volumes: + - ../plugins/codejail/apps/profiles/:/profiles/:ro + - /sys:/sys + - /etc/apparmor.d:/etc/apparmor.d diff --git a/tutorcodejail/patches/local-docker-compose-services b/tutorcodejail/patches/local-docker-compose-services index dccd85b..0ce9347 100644 --- a/tutorcodejail/patches/local-docker-compose-services +++ b/tutorcodejail/patches/local-docker-compose-services @@ -1,4 +1,19 @@ #############Codejail service +{% if CODEJAIL_USE_SERVICE_V2 %} +codejailservice: + image: {{ CODEJAIL_DOCKER_IMAGE_V2 }} + ports: + - 8550:8550 + environment: + DJANGO_SETTINGS_MODULE: codejail_service.settings.tutor + security_opt: + - apparmor:openedx_codejail_service + volumes: + - ../plugins/codejail/apps/codejail-service-v2/tutor.py:/app/codejail_service/settings/tutor.py:ro + restart: unless-stopped + depends_on: + - codejail-apparmor-loader +{% else %} codejailservice: image: {{ CODEJAIL_DOCKER_IMAGE }} environment: @@ -8,11 +23,11 @@ codejailservice: - apparmor:docker-edx-sandbox {% endif %} volumes: - - ../plugins/codejail/apps/config/tutor.py:/openedx/codejailservice/codejailservice/tutor.py:ro - - ../../data/codejail:/openedx/data + - ../plugins/codejail/apps/codejail/tutor.py:/openedx/codejailservice/codejailservice/tutor.py:ro restart: unless-stopped depends_on: - codejail-apparmor-loader +{% endif %} codejail-apparmor-loader: image: {{ CODEJAIL_APPARMOR_DOCKER_IMAGE }} diff --git a/tutorcodejail/plugin.py b/tutorcodejail/plugin.py index e3fd24e..388b0d1 100644 --- a/tutorcodejail/plugin.py +++ b/tutorcodejail/plugin.py @@ -1,12 +1,16 @@ """Manage the plugin for the tutorcodejail.""" + from __future__ import annotations import os from glob import glob from pathlib import Path +import typing as t + import importlib_resources from tutor import hooks +from tutor.types import Config from .__about__ import __version__ @@ -17,16 +21,20 @@ "SECRET_KEY": "{{ 24|random_string }}", }, "defaults": { - "APPARMOR_DOCKER_IMAGE": "docker.io/ednxops/codejail_apparmor_loader:apparmor-3", + "APPARMOR_DOCKER_IMAGE": "docker.io/ednxops/codejail_apparmor_loader:apparmor-4", "DOCKER_IMAGE": f"docker.io/ednxops/codejailservice:{__version__}", + "DOCKER_IMAGE_V2": "{{ CODEJAIL_DOCKER_IMAGE }}-v2", "ENABLE_K8S_DAEMONSET": False, "ENFORCE_APPARMOR": True, "EXTRA_PIP_REQUIREMENTS": [], "HOST": "codejailservice", "SANDBOX_PYTHON_VERSION": "3.11.14", "SERVICE_REPOSITORY": "https://github.com/edunext/codejailservice.git", + "SERVICE_V2_REPOSITORY": "https://github.com/openedx/codejail-service.git", + "SERVICE_V2_VERSION": "{{ OPENEDX_COMMON_VERSION }}", "SERVICE_VERSION": "{{ OPENEDX_COMMON_VERSION }}", "SKIP_INIT": False, + "USE_SERVICE_V2": False, "VERSION": __version__, }, "overrides": {}, @@ -35,7 +43,7 @@ def get_apparmor_abi(): """ - Return the default abi 3.0 rule if available in the system. + Return the latest default abi rule if available in the system. AppArmor uses the Policy feature ABI to establish which rules it can enforce based on the kernel capabilities. AppArmor profiles can include an @@ -43,15 +51,19 @@ def get_apparmor_abi(): AppArmor will fallback to whichever rule is pinned in the `/etc/apparmor/parser.conf` file. - We try to use the 3.0 abi whenever it's available at `/etc/apparmor.d/abi/` + We try to use at least the 3.0 abi whenever it's available at `/etc/apparmor.d/abi/` to guarantee that network rules are correctly enforced on newer versions of - the kernel. If the ABI is not present we don't set the abi rule and instead - rely on the default fallback. + the kernel. If neither the 3.0 ABI nor the 4.0 ABI are present we don't set + the abi rule and instead rely on the default fallback. See: https://github.com/netblue30/firejail/issues/3659#issuecomment-711074899 """ + if Path(f"{ABI_PATH}/4.0").exists(): + return "abi ," + if Path(f"{ABI_PATH}/3.0").exists(): return "abi ," + return "" @@ -62,45 +74,56 @@ def get_apparmor_abi(): ) -hooks.Filters.IMAGES_BUILD.add_item(( - "codejail", - ("plugins", "codejail", "build", "codejail"), - "{{ CODEJAIL_DOCKER_IMAGE }}", - (), -)) - - -hooks.Filters.IMAGES_BUILD.add_item(( - "codejail_apparmor", - ("plugins", "codejail", "build", "codejail_apparmor"), - "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", - (), -)) - - -hooks.Filters.IMAGES_PULL.add_item(( - "codejail", - "{{ CODEJAIL_DOCKER_IMAGE }}", -)) - - -hooks.Filters.IMAGES_PULL.add_item(( - "codejail_apparmor", - "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", -)) - - -hooks.Filters.IMAGES_PUSH.add_item(( - "codejail", - "{{ CODEJAIL_DOCKER_IMAGE }}", -)) - - -hooks.Filters.IMAGES_PUSH.add_item(( - "codejail_apparmor", - "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", -)) - +@hooks.Filters.IMAGES_BUILD.add() +def _build_codejail_images( + images: list[tuple[str, t.Union[str, tuple[str, ...]], str, tuple[str, ...]]], + config: Config, +): + # TODO: Remove after the Verawood update + if config.get("CODEJAIL_USE_SERVICE_V2"): + codejail_img = ( + "codejail", + "plugins/codejail/build/codejail-service", + "{{ CODEJAIL_DOCKER_IMAGE_V2 }}", + (), + ) + else: + codejail_img = ( + "codejail", + "plugins/codejail/build/codejail", + "{{ CODEJAIL_DOCKER_IMAGE }}", + (), + ) + apparmor_img = ( + "codejail_apparmor", + ("plugins", "codejail", "build", "codejail_apparmor"), + "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", + (), + ) + + return images + [codejail_img, apparmor_img] + +@hooks.Filters.IMAGES_PUSH.add() +def _push_codejail_images( + images: list[tuple[str, t.Union[str, tuple[str, ...]], str, tuple[str, ...]]], + config: Config, +): + # TODO: Remove after the Verawood update + if config.get("CODEJAIL_USE_SERVICE_V2"): + codejail_img = ( + "codejail", + "{{ CODEJAIL_DOCKER_IMAGE_V2 }}", + ) + else: + codejail_img = ( + "codejail", + "{{ CODEJAIL_DOCKER_IMAGE }}", + ) + apparmor_img = ( + "codejail_apparmor", + "{{CODEJAIL_APPARMOR_DOCKER_IMAGE}}", + ) + return images + [codejail_img, apparmor_img] # Boilerplate code # Add the "templates" folder as a template root @@ -121,15 +144,9 @@ def get_apparmor_abi(): hooks.Filters.ENV_PATCHES.add_item((os.path.basename(path), patch_file.read())) # Add configuration entries hooks.Filters.CONFIG_DEFAULTS.add_items( - [ - (f"CODEJAIL_{key}", value) - for key, value in config.get("defaults", {}).items() - ] + [(f"CODEJAIL_{key}", value) for key, value in config.get("defaults", {}).items()] ) hooks.Filters.CONFIG_UNIQUE.add_items( - [ - (f"CODEJAIL_{key}", value) - for key, value in config.get("unique", {}).items() - ] + [(f"CODEJAIL_{key}", value) for key, value in config.get("unique", {}).items()] ) hooks.Filters.CONFIG_OVERRIDES.add_items(list(config.get("overrides", {}).items())) diff --git a/tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py b/tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py new file mode 100644 index 0000000..172a72b --- /dev/null +++ b/tutorcodejail/templates/codejail/apps/codejail-service-v2/tutor.py @@ -0,0 +1,34 @@ +from codejail_service.settings.local import * # pylint: disable=wildcard-import + +ALLOWED_HOSTS = [ + 'codejailservice', + 'localhost', +] + +CODEJAIL_ENABLED = True +SECRET_KEY = '{{ CODEJAIL_SECRET_KEY }}' + +CODE_JAIL = { + 'python_bin': '/sandbox/venv/bin/python', + 'user': 'sandbox', + + # Configurable limits. + 'limits': { + # CPU-seconds + 'CPU': 3, + # Clock seconds + 'REALTIME': 3, + # Need at least 300 MiB memory for matplotlib alone. 512 MiB should be + # enough headroom in general. + 'VMEM': 512 * 1024 * 1024, + # 10 MB file size limit + 'FSIZE': 10 * 1024 * 1024, + # 15 processes and threads (codejail default) + 'NPROC': 15, + # Don't use a proxy process to spawn subprocesses. + 'PROXY': 0, + }, +} + +{{ patch("codejail-common-settings") }} +{{ patch("codejail-production-settings") }} diff --git a/tutorcodejail/templates/codejail/apps/config/tutor.py b/tutorcodejail/templates/codejail/apps/codejail/tutor.py similarity index 100% rename from tutorcodejail/templates/codejail/apps/config/tutor.py rename to tutorcodejail/templates/codejail/apps/codejail/tutor.py diff --git a/tutorcodejail/templates/codejail/apps/profiles/docker-edx-sandbox b/tutorcodejail/templates/codejail/apps/profiles/docker-edx-sandbox.profile similarity index 100% rename from tutorcodejail/templates/codejail/apps/profiles/docker-edx-sandbox rename to tutorcodejail/templates/codejail/apps/profiles/docker-edx-sandbox.profile diff --git a/tutorcodejail/templates/codejail/apps/profiles/openedx-codejail-service.profile b/tutorcodejail/templates/codejail/apps/profiles/openedx-codejail-service.profile new file mode 100644 index 0000000..44beb42 --- /dev/null +++ b/tutorcodejail/templates/codejail/apps/profiles/openedx-codejail-service.profile @@ -0,0 +1,132 @@ +# AppArmor profile for running codejail-service. +# +# Changes to this profile must be coordinated carefully with changes to the +# Dockerfile. See README for additional cautions. +# +# #=========# +# # WARNING # +# #=========# +# +# Failure to apply a secure apparmor profile *will* likely result in a +# full compromise of the host by an attacker. AppArmor is *mandatory* +# for using codejail -- this is not just for hardening. +# +# This profile is written for AppArmor 4 or higher (Ubuntu 24.04 or higher). + + + +# Sets standard variables used by abstractions/base, later. Controlled +# by OS, see /etc/apparmor.d/tunables/global for contents. +include + +# Require that the system understands the feature set that this policy was written +# for. If we didn't include this, then on Ubuntu >= 22.04, AppArmor might assume +# the wrong feature set was requested, and some rules might become too permissive. +# See https://github.com/netblue30/firejail/issues/3659#issuecomment-711074899 +abi , + +# This outer profile applies to the entire container, and isn't as +# important as the inner (codejail_sandbox) profile. If the inner profile doesn't work, it's not likely that +# the outer one is going to help. But there may be some small value in +# defense-in-depth, as it's possible that a bug in the codejail_sandbox (inner) +# profile isn't present in the outer one. +# +# The use of `attach_disconnected.path` is required in the outer profile to +# allow accessing the unix domain socket that Datadog creates at +# `/var/run/datadog/apm.socket`. This is a fd that is not mounted in the +# filesystem (AppArmor sees it as `var/run/datadog/apm.socket` with no leading +# slash) and so this is our only option for allowing AppArmor to mediate (and +# allow) access to it. With this options, we would be able to safely refer in +# the profile to `/disconnected/var/run/datadog/apm.socket`. Other disconnected +# objects could masquerade as this one, but not as paths in the wider +# filesystem. WARNING: Plain `attach_disconnected` (without the path) *would not +# be safe* in any profile that includes file paths, as it can create dangerous +# and hard-to-understand path aliasing for disconnected objects. However, the +# outer profile already broadly allows `file`, and the only path mentioned is +# for sandbox execution, which is a transition to a *more* restrictive +# profile. It might be acceptable, but the `.path` version (new in AppArmor 4) +# is much safer. +profile openedx_codejail_service flags=(mediate_deleted, attach_disconnected.path=/disconnected) { + + # Allow access to a variety of commonly needed, generally safe things + # (such as reading /dev/random, free memory, etc.) + # + # Manpage: "Includes files that should be readable and writable in all profiles." + include + + # Filesystem access -- self-explanatory + file, + + # netlink is needed for sudo's interprocess communication + network netlink raw, + + # Allow all of the various network operations required to listen, accept connection, etc. + network tcp, + # But then deny making a new *outbound* connection. + deny network (connect) tcp, + + # Required for sudoing to sandbox + capability setuid setgid audit_write, + # Allow sending a kill signal + capability kill, + + # Allow sending a kill signal to the codejail_sandbox subprofile when the execution + # runs beyond time limits. + signal (send) set=(kill) peer=openedx_codejail_service//codejail_sandbox, + + # Allow receiving a kill signal (and other signals) from container orchestration. + signal (receive), + + # The core of the confinement: When the sandbox Python is executed, switch to + # the (extremely constrained) codejail_sandbox profile. + # + # This path needs to be coordinated with the Dockerfile and Django settings. + # + # Manpage: "Cx: transition to subprofile on execute -- scrub the environment" + /sandbox/venv/bin/python Cx -> codejail_sandbox, + + # This is the important apparmor profile -- the one that actually + # constrains the sandbox Python process. + # + # `mediate_deleted` instructs apparmor to continue to make policy decisions + # in cases where a confined executable has a file descriptor even after the + # file is removed from the filesystem. It's unclear if this is important for + # sandboxing, but it doesn't seem like it would loosen security or interfere + # with functionality to include it, so we have. + # + # `no_attach_disconnected` is default, but is explicitly indicated on the + # inner profile because `attach_disconnected` is very commonly used in + # example profiles despite being a security risk (due to allowing + # disconnected objects to masquerade as other, trusted paths in the + # filesystem). + profile codejail_sandbox flags=(mediate_deleted, no_attach_disconnected) { + + # This inner profile also gets general access to "safe" + # actions; we could list those explicitly out of caution but + # it could get pretty verbose. + include + + # Read and run binaries and libraries in the virtualenv. This + # includes the sandbox's copy of Python as well as any + # dependencies that have been installed for inclusion in + # sandboxes. + # + # m: executable mapping, required for shared libraries used by some + # Python dependencies with C compontents, eg `nltk`. + /sandbox/venv/** rm, + + # Allow access to the temporary directories that are set up by + # codejail, one for each code-exec call. Each /tmp/code-XXXXX + # contains one execution. + # + # Codejail has a hardcoded reference to this file path, although the + # use of /tmp specifically may be controllable with environment variables: + # https://github.com/openedx/codejail/blob/0165d9ca351/codejail/util.py#L15 + /tmp/codejail-*/ r, + /tmp/codejail-*/** rw, + + # Allow receiving a kill signal from the webapp when the execution + # runs beyond time limits. + signal (receive) set=(kill) peer=openedx_codejail_service, + } +} diff --git a/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile b/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile new file mode 100644 index 0000000..233d829 --- /dev/null +++ b/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile @@ -0,0 +1,173 @@ +FROM scratch AS codejail-service-code +ARG CODEJAIL_SERVICE_REPO={{ CODEJAIL_SERVICE_V2_REPOSITORY }} +ARG CODEJAIL_SERVICE_VERSION={{ CODEJAIL_SERVICE_V2_VERSION }} +ADD ${CODEJAIL_SERVICE_REPO}#${CODEJAIL_SERVICE_VERSION} / + +FROM scratch AS sandbox-dependencies +# Where to get the Python dependencies lockfile for installing +# packages into the sandbox environment. Defaults to the codejail +# dependencies in edx-platform. +ARG SANDBOX_DEPS_REPO=openedx/edx-platform +ARG SANDBOX_DEPS_VERSION={{ CODEJAIL_SERVICE_V2_VERSION }} +# Path to the lockfile in the deps repo, as dir + filename. +# +# The path base.txt will get the latest dependencies, but this needs +# to be coordinated with SANDBOX_PY_VER as each release has a +# different Python support window. +ARG SANDBOX_DEPS_SRC_DIR=requirements/edx-sandbox +ADD https://github.com/${SANDBOX_DEPS_REPO}.git#${SANDBOX_DEPS_VERSION}:${SANDBOX_DEPS_SRC_DIR} / + +FROM docker.io/ubuntu:24.04 AS app +ARG APP_PY_VER=3.12 +# See codejail-service deployment and configuration docs for why we need to select +# a UID/GID that is unlikely to collide with anything on the host. (Short answer: +# RLIMIT_NPROC UID-global usage pool, and Docker not isolating UIDs.) +# +# Selected via: python3 -c 'import random; print(random.randrange(3000, 2 ** 31))' +ARG APP_UID=15826504 +ARG APP_GID=$APP_UID + +ARG SANDBOX_DEPS_SRC_FILE=base.txt + +# Python version for sandboxed executions. This must be coordinated with +# `SANDBOX_DEPS_SRC_*` to ensure compatibility. +ARG SANDBOX_PY_VER={{ '.'.join(CODEJAIL_SANDBOX_PYTHON_VERSION.split('.')[:2]) }} + + +##### Base app installation ##### + +ENV DEBIAN_FRONTEND=noninteractive +ARG APT_INSTALL="apt-get install --quiet --yes --no-install-recommends" + +# The codejail library specifies a certain structure to how the sandboxing is +# performed. (See the documentation in the codejail library README: +# https://github.com/openedx/codejail). +# +# Some of this structure can be changed, and some cannot. Any changes that are +# possible will also need to be coordinated with changes to the apparmor profile +# as well as to the `CODE_JAIL` Django settings. Accordingly, it's best to just +# *avoid* making changes to this part. + +# The location of the virtualenv that code executions in the sandbox will use. +# This is a critical path, as SAND_VENV/bin/python is what is targeted by the +# AppArmor confinement. It must also match the Django setting +# `CODE_JAIL.python_bin`. The codejail docs refer to this as ``. +ARG SAND_VENV=/sandbox/venv +# The user account that will run code executions, described just as "the sandbox +# user" in codejail docs. This needs to match the Django setting +# `CODE_JAIL.user` and the sudoers file. +ARG SAND_USER=sandbox +# Same situation as for APP_UID +ARG SAND_UID=33552349 +ARG SAND_GID=$SAND_UID +# The user account that runs the regular web app, described in codejail docs as +# ``. Needs to match the sudoers file. +ARG APP_USER=app + +# The codejail-service API tests check for the visibility of this environment +# variable from the sandbox. (It should not be visible.) This helps test for +# environment leakage into the sandbox. +ENV CJS_TEST_ENV_LEAKAGE=yes + +# Packages installed: +# +# - language-pack-en, locales: Ubuntu locale support so that system utilities +# have a consistent language and time zone. +# - sudo: Web user (`APP_USER`) needs to be able to sudo as `SAND_USER` +# - python*: Specific versions of Python -- the service runs with a recent version, but +# the sandboxed code will usually need a different (older) version. This is also why +# we need to pull in the deadsnakes PPA. +# - python*-dev: Header files for python extensions, required by many source wheels +# - python*-venv: Allow creation of virtualenvs +# +# We also have to do a bit of bootstrapping here installing the +# `software-properties-common` package gives us `add-apt-repository`, which +# allows us to add the deadsnakes PPA more easily (that is, without messing +# about with repository keys). +RUN < Date: Fri, 21 Nov 2025 17:55:37 -0400 Subject: [PATCH 4/4] feat: support the openedx/codejail-service implementation --- README.rst | 18 +++++++++++++++--- .../patches/local-docker-compose-services | 2 +- .../codejail/build/codejail-service/Dockerfile | 4 ++-- 3 files changed, 18 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index e329eb1..7118041 100644 --- a/README.rst +++ b/README.rst @@ -1,8 +1,14 @@ Codejail plugin for `Tutor`_ ============================ -Tutor plugin that configures and runs a `Codejail Service`_ using a REST API. `Codejail`_ allows for the -secure execution of untrusted code within sandboxes, providing a safe environment for running potentially dangerous code. +Tutor plugin that configures and runs a `Codejail Service`_ using a REST API. +`Codejail`_ allows for the secure execution of untrusted code within sandboxes, +providing a safe environment for running potentially dangerous code. + +Starting from the Ulmo release, the codejail plugin is transitioning to an +alternative implementation of the safe-exec API ( `_Codejail Service V2`_). +You can opt-in to use this new implementation on Ulmo before it finally becomes +the default on the Verawood release. .. _Tutor: https://docs.tutor.overhang.io .. _Codejail Service: https://github.com/eduNEXT/codejailservice @@ -69,7 +75,13 @@ To customize the configuration, update the following settings in Tutor: - ``SERVICE_V2_VERSION``: (default: ``{{ OPENEDX_COMMON_VERSION }}``) - ``USE_SERVICE_V2``: (default: ``False``) -The ``CODEJAIL_V2_*`` settings are meant to be used only during the Ulmo release a +The ``CODEJAIL_V2_*`` settings are meant to be used only during the Ulmo +release and will be phased-out during the Verawood release. + +To opt-in to the new implementation of the code-exec API set ``USE_SERVICE_V2`` +to ``True`` and re-deploy your environment. If you are using a a custom image +for the codejail service you will need to rebuild it with ``USE_SERVICE_V2`` +set to ``True`. Custom Image ~~~~~~~~~~~~ diff --git a/tutorcodejail/patches/local-docker-compose-services b/tutorcodejail/patches/local-docker-compose-services index 0ce9347..b3dceb8 100644 --- a/tutorcodejail/patches/local-docker-compose-services +++ b/tutorcodejail/patches/local-docker-compose-services @@ -38,6 +38,6 @@ codejail-apparmor-loader: - -v=2 - /profiles volumes: - - ../plugins/codejail/apps/profiles/docker-edx-sandbox:/profiles/docker-edx-sandbox:ro + - ../plugins/codejail/apps/profiles/:/profiles/:ro - /sys:/sys - /etc/apparmor.d:/etc/apparmor.d diff --git a/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile b/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile index 233d829..a62f8cd 100644 --- a/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile +++ b/tutorcodejail/templates/codejail/build/codejail-service/Dockerfile @@ -7,7 +7,7 @@ FROM scratch AS sandbox-dependencies # Where to get the Python dependencies lockfile for installing # packages into the sandbox environment. Defaults to the codejail # dependencies in edx-platform. -ARG SANDBOX_DEPS_REPO=openedx/edx-platform +ARG SANDBOX_DEPS_REPO={{ EDX_PLATFORM_REPOSITORY }} ARG SANDBOX_DEPS_VERSION={{ CODEJAIL_SERVICE_V2_VERSION }} # Path to the lockfile in the deps repo, as dir + filename. # @@ -15,7 +15,7 @@ ARG SANDBOX_DEPS_VERSION={{ CODEJAIL_SERVICE_V2_VERSION }} # to be coordinated with SANDBOX_PY_VER as each release has a # different Python support window. ARG SANDBOX_DEPS_SRC_DIR=requirements/edx-sandbox -ADD https://github.com/${SANDBOX_DEPS_REPO}.git#${SANDBOX_DEPS_VERSION}:${SANDBOX_DEPS_SRC_DIR} / +ADD ${SANDBOX_DEPS_REPO}#${SANDBOX_DEPS_VERSION}:${SANDBOX_DEPS_SRC_DIR} / FROM docker.io/ubuntu:24.04 AS app ARG APP_PY_VER=3.12