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
3 changes: 3 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ ci_requirements: validation_requirements ## sync to requirements needed for CI c
dev_requirements: clean_tox piptools ## sync to requirements for local development
pip-sync -q requirements/dev.txt requirements/private.*

prod_requirements: piptools ## sync virtualenv to requirements needed for production
pip-sync -q requirements/base.txt

validation_requirements: piptools ## sync to requirements for testing & code quality checking
pip-sync -q requirements/validation.txt

Expand Down
21 changes: 2 additions & 19 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,9 @@ This service is configured with an in-memory database simply to make Django happ
Operators
=========

This is intended to be run as a fully internal service with no database or admin frontend, with the LMS and CMS making calls to it unauthenticated. **It should not be callable directly from the internet.**
**It is critical to configure this service securely**, as a misconfigured codejail-service will almost certainly allow an attacker to compromise not just this service, but possibly the rest of your infrastructure. See configuration and deployment docs for details.

While the service uses AppArmor as a first (and really, only) line of defense, it should also be deployed in a way that does not allow direct connections to other IDAs or internal services within the Open edX deployment. In the ideal situation, networking would be set up to only allow outbound connections to a predetermined set of IPs or domains.

After any significant change to security settings, consider running the tests in ``./api_tests/``. These are run manually against a deployed service and can check for various sandbox weaknesses.
This is intended to be run as a fully internal service with no database or admin frontend, with the LMS and CMS making calls to it unauthenticated. It should not be callable directly from the internet.

Getting Started with Development
********************************
Expand All @@ -33,21 +31,6 @@ Please see the Open edX documentation for `guidance on Python development`_ in t

.. _guidance on Python development: https://docs.openedx.org/en/latest/developers/how-tos/get-ready-for-python-dev.html

Deploying
*********

The service may be started using gunicorn with a command like this::

export DJANGO_SETTINGS_MODULE=codejail_service.settings.production
gunicorn -c codejail_service/docker_gunicorn_configuration.py \
--bind '0.0.0.0:8080' --workers=10 --max-requests=1000 --name codejail \
codejail_service.wsgi:application

There is a healthcheck endpoint at ``/health/`` which responds to a
GET with ``200 OK`` if the service is running and healthy.

TODO (`<https://github.com/openedx/codejail-service/issues/3>`__)

Getting Help
************

Expand Down
2 changes: 0 additions & 2 deletions docs/concepts/index.rst

This file was deleted.

25 changes: 0 additions & 25 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ def get_version(*file_paths):
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
'sphinx.ext.autodoc',
'sphinx.ext.doctest',
'sphinx.ext.intersphinx',
'sphinx.ext.ifconfig',
Expand Down Expand Up @@ -514,27 +513,3 @@ def get_version(*file_paths):
intersphinx_mapping = {
'python': ('https://docs.python.org/3.12', None),
}


def on_init(app): # pylint: disable=unused-argument
"""
Run sphinx-apidoc after Sphinx initialization.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. Are swagger docs completely separate from all this?
  2. How does the LMS generate these docs? https://docs.openedx.org/projects/edx-platform/en/latest/references/lms_apis.html. Wouldn't we want something similar for this service?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Swagger is separate, yeah. I'll add that to #3 for future work.


Read the Docs won't run tox or custom shell commands, so we need this to
avoid checking in the generated reStructuredText files.
"""
docs_path = os.path.abspath(os.path.dirname(__file__))
root_path = os.path.abspath(os.path.join(docs_path, '..'))
apidoc_path = 'sphinx-apidoc'
if hasattr(sys, 'real_prefix'): # Check to see if we are in a virtualenv
# If we are, assemble the path manually
bin_path = os.path.abspath(os.path.join(sys.prefix, 'bin'))
apidoc_path = os.path.join(bin_path, apidoc_path)
check_call([apidoc_path, '-o', docs_path, os.path.join(root_path, 'codejail_service'),
os.path.join(root_path, 'codejail_service/migrations')])


def setup(app):
"""Sphinx extension: run sphinx-apidoc."""
event = 'builder-inited'
app.connect(event, on_init)
66 changes: 66 additions & 0 deletions docs/deployment.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
Configuration and deployment
############################

codejail-service has some unusual requirements due to its sensitive nature. **Please read all sections carefully.**

Before you begin
****************

Ensure your intended deployment environment meets the following prerequisites:

* Ubuntu, Debian, or another Linux distribution that supports AppArmor
* AppArmor 3 or newer (e.g. in Ubuntu 22.04 or newer)
* The ability to load an AppArmor profile into the host and (if using Docker) apply the profile to a Docker container

Network
*******

If at all possible, your codejail-service cluster should be run with the following networking restrictions:

* No inbound access from the public internet. codejail-service lacks authentication, rate-limiting, audit logs, and other protections, and its functionality should only be exposed via Custom Python-evaluated Input Problems in the LMS and CMS.
* No outbound connections, *especially* to other services within your deployment. This will help limit the impact if an attacker were to figure out how to escape the sandbox.

Host
****

The host or Docker image must be set up with a sandbox user, sandbox virtualenv, and sudoers file according to the documentation of the `codejail library <https://github.com/openedx/codejail>`__. This forms the foundation of the sandboxing, but does not provide much security by itself.

See `2U's Dockerfile <https://github.com/edx/public-dockerfiles/blob/main/dockerfiles/codejail-service.Dockerfile>`__ for an example of how you could set up your host or container. (As of March 2025, there is `not yet a Tutor plugin <https://github.com/openedx/codejail-service/issues/26>`__.)

AppArmor
********

Again following the codejail library's instructions, create an AppArmor profile and load it into the host.

This profile will need to be customized according to your service's app user, sandbox setup, and other details. It will also look different if you are using Docker or running the service directly on the host. For reference, here is `2U's AppArmor profile <https://github.com/edx/public-dockerfiles/blob/main/apparmor/openedx_codejail_service.profile>`__; note that the inner profile is the one that actually applies the sandboxing.

Django settings
***************

Now you can set your ``CODE_JAIL`` Django setting, which tells the codejail library where the sandboxed Python executable lives, and how to limit resource usage. See the codejail library's documentation for details. (Note: Leave the ``PROXY`` setting to its default of ``0``.)

Starting the service
********************

To run the service, activate a virtualenv, run ``make prod_requirements``, and start the service with ``gunicorn`` using a command like this::

export DJANGO_SETTINGS_MODULE=codejail_service.settings.production
gunicorn -c codejail_service/docker_gunicorn_configuration.py \
--bind '0.0.0.0:8080' --workers=10 --max-requests=1000 --name codejail \
codejail_service.wsgi:application

The service is now listening on port 8080.

There is a healthcheck endpoint at ``/health/`` which responds to a GET with ``200 OK`` if the service is running and healthy, or a ``503 Service Unavailable`` otherwise. The healthcheck is driven by a handful of security tests that the service performs. If the healthcheck is failing, this likely indicates that there is a misconfiguration that would allow unsandboxed code execution.

Enabling code execution
***********************

Once the healthcheck is passing (indicating that sandboxing is probably functional), the main code-exec endpoint can be enabled with a Django setting: ``CODEJAIL_ENABLED = True``. This should not be enabled until the healthcheck passes, and should be immediately followed with API tests (see next section), which include a more comprehensive set of tests than the healthcheck performs.

API tests
*********

After the first setup, and after any significant change to security settings, run the tests in ``./api_tests/`` (see README in that directory). This will probe the service for a variety of possible vulnerabilities.

These tests can also be incorporated into your deployment pipeline.
30 changes: 0 additions & 30 deletions docs/features.rst

This file was deleted.

18 changes: 0 additions & 18 deletions docs/getting_started.rst

This file was deleted.

2 changes: 0 additions & 2 deletions docs/how-tos/index.rst

This file was deleted.

12 changes: 2 additions & 10 deletions docs/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,22 @@
codejail_service
================

Run codejail (sandboxed Python execution) as a service
Run codejail (sandboxed Python execution) as a service.

Contents:

.. toctree::
:maxdepth: 2

readme
getting_started
quickstarts/index
concepts/index
how-tos/index
testing
internationalization
modules
deployment
changelog
decisions
features
references/index


Indices and tables
##################

* :ref:`genindex`
* :ref:`modindex`
* :ref:`search`
49 changes: 0 additions & 49 deletions docs/internationalization.rst

This file was deleted.

2 changes: 0 additions & 2 deletions docs/quickstarts/index.rst

This file was deleted.

2 changes: 0 additions & 2 deletions docs/references/index.rst

This file was deleted.

13 changes: 0 additions & 13 deletions docs/testing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,12 @@ To run just the unit tests:

$ make test

To run just the unit tests and check diff coverage

.. code-block:: bash

$ make diff_cover

To run just the code quality checks:

.. code-block:: bash

$ make quality

To run the unit tests under every supported Python version and the code
quality checks:

.. code-block:: bash

$ make test-all

To generate and open an HTML report of how much of the code is covered by
test cases:

Expand Down
2 changes: 0 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,6 @@ deps =
-r{toxinidir}/requirements/doc.txt
commands =
doc8 --ignore-path docs/_build README.rst docs
rm -f docs/codejail_service.rst
rm -f docs/modules.rst
make -e -C docs clean
make -e -C docs html

Expand Down