Skip to content

Commit f395882

Browse files
committed
feat: AppArmor confinement for codejail-service
Allow codejail-service to actually run code (and run it securely) by giving it the needed confinement. Previously it would run but refuse to execute code since it would detect the insecure environment; now, the startup safety checks pass and the code-exec endpoint works as expected. - Add an AppArmor profile with fairly strict rules. It needs to be thoroughly vetted and to have exceptions added before it can be used in production, but it's be fine for devstack. Some parts are based on the existing edxapp apparmor config without careful review. - Apply the profile to the codejail service in docker-compose. - Add Django configs for codejail service. - Add documentation for installing the profile so that it is available for use on the dev's machine. Also: - Add configuration and documentation for edxapp to actually call the codejail service, disabled by default. (Will later want to make this default to true, once the service is working properly.) - Update image name in docker-compose to follow rename in edx/public-dockerfiles#102 Currently edxapp gets an error back from codejail-service, and then isn't able to read that error; separate work in the app repo will be needed to fix those. (The first issue relates to python_path, and the other to not returning globals_dict when there's an emsg.) But the integration is working otherwise.
1 parent 523ec82 commit f395882

File tree

7 files changed

+160
-1
lines changed

7 files changed

+160
-1
lines changed

codejail.profile

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# AppArmor profile for running codejail-service in devstack.
2+
#
3+
# #=========#
4+
# # WARNING #
5+
# #=========#
6+
#
7+
# This is not a complete and secure apparmor profile! Do not use this
8+
# in any deployed environment (even a staging environment) without
9+
# careful inspection and modification to fit your needs.
10+
#
11+
# Failure to apply a secure apparmor profile *will* likely result in a
12+
# compromise of your environment by an attacker.
13+
#
14+
# We may at some point make this file good enough for confinement in
15+
# production, but for now it is only intended to be used in devstack.
16+
17+
18+
#include <tunables/global>
19+
20+
# Declare ABI version explicitly to ensure that confinement is
21+
# actually applied appropriately on newer Ubuntu.
22+
abi <abi/3.0>,
23+
24+
# This outer profile applies to the entire container, and isn't as
25+
# important. If the sandbox profile doesn't work, it's not likely that
26+
# the outer one is going to help. But there may be some small value in
27+
# defense-in-depth, as it's possible that a bug in the child (sandbox)
28+
# profile isn't present in the outer one.
29+
profile codejail_service flags=(attach_disconnected,mediate_deleted) {
30+
#include <abstractions/base>
31+
32+
# Filesystem access -- self-explanatory
33+
file,
34+
35+
# `network` is required for sudo
36+
# TODO: Restrict this so that general network access is not permitted
37+
network,
38+
39+
# Various capabilities required for sudoing to sandbox (setuid,
40+
# setgid, audit_write) and for sending a kill signal (kill).
41+
capability setuid setgid audit_write kill,
42+
43+
# Allow sending a kill signal to the sandbox when the execution
44+
# runs beyond time limits.
45+
signal (send) set=(kill) peer=codejail_service//child,
46+
47+
# Allow executing this binary, but force a transition to the specified
48+
# profile (and scrub the environment).
49+
/sandbox/venv/bin/python Cx -> child,
50+
51+
# This is the important apparmor profile -- the one that actually
52+
# constrains the sandbox Python process.
53+
profile child flags=(attach_disconnected,mediate_deleted) {
54+
#include <abstractions/base>
55+
56+
# Read and run binaries and libraries in the virtualenv. This
57+
# includes the sandbox's copy of Python as well as any
58+
# dependencies that have been installed for inclusion in
59+
# sandboxes.
60+
/sandbox/venv/** rm,
61+
62+
# Codejail has a hardcoded reference to this file path, although the
63+
# use of /tmp specifically may be controllable with environment variables:
64+
# https://github.com/openedx/codejail/blob/0165d9ca351/codejail/util.py#L15
65+
/tmp/codejail-*/ r,
66+
/tmp/codejail-*/** rw,
67+
68+
# Allow interactive terminal during development
69+
/dev/pts/* rw,
70+
71+
# Allow receiving a kill signal from the webapp when the execution
72+
# runs beyond time limits.
73+
signal (receive) set=(kill) peer=codejail_service,
74+
}
75+
}

docker-compose.yml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -726,11 +726,13 @@ services:
726726
hostname: codejail.devstack.edx
727727
stdin_open: true
728728
tty: true
729-
image: edxops/codejail-dev:latest
729+
image: edxops/codejail-service-dev:latest
730730
environment:
731731
DJANGO_SETTINGS_MODULE: codejail_service.settings.devstack
732732
ports:
733733
- "18030:8080"
734+
security_opt:
735+
- apparmor=codejail_service
734736

735737
xqueue:
736738
container_name: "edx.${COMPOSE_PROJECT_NAME:-devstack}.xqueue"

docs/codejail.rst

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
Codejail service
2+
################
3+
4+
The ``codejail`` devstack component (codejail-service) requires some additional configuration before it can be enabled. This page describes how to set it up and debug it.
5+
6+
Background
7+
**********
8+
9+
Both LMS and CMS can run Python code submitted by instructors and learners in order to implement custom Python-graded problems. By default this involves running the code on the same host as edxapp itself. Ordinarily this would be quite dangerous, but we use a sandboxing library called `codejail <https://github.com/openedx/codejail>`__ in order to confine the code execution in terms of disk and network access as well as memory, CPU, and other resource limits. Part of these restrictions are implemented via AppArmor, a utility available in some Linux distributions (including Debian and Ubuntu).
10+
11+
While AppArmor provides good protection, a sandbox escape could still be possible due to misconfiguration or bugs in AppArmor. For defense in depth, we're setting up a dedicated `codejail service <https://github.com/openedx/codejail-service>`__ that will perform code execution for edxapp and which will allow further isolation.
12+
13+
The default edxapp codejail defaults to unsafe, direct execution of Python code, and this remains true in devstack. We don't even have a way to run on-host codejail securely in devstack. In constrast, the codejail service refuses to run if codejail has not been configured properly, and we've included a way to run it in devstack.
14+
15+
Configuration
16+
*************
17+
18+
In order to run the codejail devstack component:
19+
20+
1. Install AppArmor: ``sudo apt install apparmor``
21+
2. Add the codejail AppArmor profile to your OS, or update it: ``sudo apparmor_parser --replace -W codejail.profile``
22+
3. Configure LMS and CMS to use the codejail-service by changing ``ENABLE_CODEJAIL_REST_SERVICE`` to ``True`` in ``py_configuration_files/{lms,cms}.py``
23+
4. Run ``make codejail-up``
24+
25+
The service does not need any provisioning, and does not have dependencies.
26+
27+
Development
28+
***********
29+
30+
Changes to the AppArmor profile must be coordinated with changes to the Dockerfile, as they need to agree on filesystem paths.
31+
32+
Any time you update the profile, you'll need to re-run the command to apply the profile.
33+
34+
The profile file contains the directive ``profile codejail_service``. That defines the name of the profile when it is installed into the kernel. In order to change that name, you must first remove the profile **under the old name**, then install a new profile under the new name. To remove a profile, use the ``--remove`` action instead of the ``-replace`` action: : ``sudo apparmor_parser --remove -W codejail.profile``
35+
36+
The profile name must also agree with the relevant ``security_opt`` line in devstack's ``docker-compose.yml``.
37+
38+
Debugging
39+
*********
40+
41+
To check whether the profile has been applied, run ``sudo aa-status | grep codejail``. This won't tell you if the profile is out of date, but it will tell you if you have *some* version of it installed.
42+
43+
If you need to debug the confinement, either because it is restricting too much or too little, a good strategy is to run ``tail -F /var/log/kern.log | grep codejail`` and watch for ``DENIED`` lines. You should expect to see several appear during service startup, as the service is designed to probe the confinement as part of its initial healthcheck.

docs/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,3 +27,4 @@ Contents
2727
troubleshoot_general_tips
2828
manual_upgrades
2929
advanced_configuration
30+
codejail

py_configuration_files/cms.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,13 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing
312312
xblock_duplicated_event_setting = EVENT_BUS_PRODUCER_CONFIG['org.openedx.content_authoring.xblock.duplicated.v1']
313313
xblock_duplicated_event_setting['course-authoring-xblock-lifecycle']['enabled'] = True
314314

315+
############################ Codejail ############################
316+
317+
# Disabled by default since codejail service needs to be configured
318+
# and started separately. See docs/codejail.rst for details.
319+
ENABLE_CODEJAIL_REST_SERVICE = False
320+
CODE_JAIL_REST_SERVICE_HOST = "http://edx.devstack.codejail:8080"
321+
315322

316323
################# New settings must go ABOVE this line #################
317324
########################################################################

py_configuration_files/codejail.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,28 @@
11
"""Settings for devstack use."""
22

33
from codejail_service.settings.local import * # pylint: disable=wildcard-import
4+
5+
ALLOWED_HOSTS = [
6+
# When called from outside of docker's network (dev's terminal)
7+
'localhost',
8+
# When called from another container (lms, cms)
9+
'edx.devstack.codejail',
10+
]
11+
12+
CODEJAIL_ENABLED = True
13+
14+
CODE_JAIL = {
15+
# These values are coordinated with the Dockerfile and AppArmor profile
16+
'python_bin': '/sandbox/venv/bin/python',
17+
'user': 'sandbox',
18+
19+
# Configurable limits.
20+
'limits': {
21+
# CPU-seconds
22+
'CPU': 3,
23+
# 100 MiB memory
24+
'VMEM': 100 * 1024 * 1024,
25+
# Clock seconds
26+
'REALTIME': 3,
27+
},
28+
}

py_configuration_files/lms.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,12 @@ def should_show_debug_toolbar(request): # lint-amnesty, pylint: disable=missing
554554
'http://localhost:1996', # frontend-app-learner-dashboard
555555
]
556556

557+
############################ Codejail ############################
558+
559+
# Disabled by default since codejail service needs to be configured
560+
# and started separately. See docs/codejail.rst for details.
561+
ENABLE_CODEJAIL_REST_SERVICE = False
562+
CODE_JAIL_REST_SERVICE_HOST = "http://edx.devstack.codejail:8080"
557563

558564
################# New settings must go ABOVE this line #################
559565
########################################################################

0 commit comments

Comments
 (0)