Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 25 additions & 6 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
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
.. _Codejail Service V2: https://github.com/openedx/codejail-service
.. _Codejail: https://github.com/openedx/codejail

Installation
Expand All @@ -23,9 +30,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
-----
Expand Down Expand Up @@ -55,14 +62,26 @@ 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 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
~~~~~~~~~~~~
Expand Down
38 changes: 38 additions & 0 deletions tutorcodejail/patches/k8s-deployments
Original file line number Diff line number Diff line change
@@ -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:
Expand Down Expand Up @@ -36,6 +73,7 @@ spec:
- name: settings-codejail
configMap:
name: settings-codejail
{% endif %}
{% if CODEJAIL_ENABLE_K8S_DAEMONSET %}
---
apiVersion: apps/v1
Expand Down
2 changes: 2 additions & 0 deletions tutorcodejail/patches/k8s-services
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ apiVersion: v1
kind: Service
metadata:
name: codejailservice
labels:
app.kubernetes.io/name: codejailservice
spec:
type: ClusterIP
ports:
Expand Down
9 changes: 7 additions & 2 deletions tutorcodejail/patches/kustomization-configmapgenerator
Original file line number Diff line number Diff line change
@@ -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
9 changes: 9 additions & 0 deletions tutorcodejail/patches/local-docker-compose-jobs-services
Original file line number Diff line number Diff line change
@@ -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
21 changes: 18 additions & 3 deletions tutorcodejail/patches/local-docker-compose-services
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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 }}
Expand All @@ -23,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
121 changes: 69 additions & 52 deletions tutorcodejail/plugin.py
Original file line number Diff line number Diff line change
@@ -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__

Expand All @@ -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": {},
Expand All @@ -35,23 +43,27 @@

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
ABI rule to indicate the ABI they were developed under. If no rule is used
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 <abi/4.0>,"

if Path(f"{ABI_PATH}/3.0").exists():
return "abi <abi/3.0>,"

return ""


Expand All @@ -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
Expand All @@ -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()))
Loading
Loading