Skip to content

Commit 483c6f8

Browse files
timmc-edxrobrap
andauthored
feat: AppArmor confinement for codejail-service (#109)
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. - 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 relating to python_path: `No such file or directory: "/edx/var/edxapp/data/edX/DemoX/Demo_Course"` But the integration is working otherwise. --------- Co-authored-by: Robert Raposa <[email protected]>
1 parent 971cbe2 commit 483c6f8

File tree

7 files changed

+205
-1
lines changed

7 files changed

+205
-1
lines changed

Diff for: codejail.profile

+113
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
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+
# See https://manpages.ubuntu.com/manpages/noble/man5/apparmor.d.5.html
12+
# or `man apparmor.d` for documentation of syntax and options.
13+
#
14+
# Failure to apply a secure apparmor profile *will* likely result in a
15+
# compromise of your environment by an attacker.
16+
#
17+
# We may at some point make this file good enough for confinement in
18+
# production, but for now it is only intended to be used in devstack.
19+
20+
21+
22+
# Sets standard variables used by abstractions/base, later. Controlled
23+
# by OS, see /etc/apparmor.d/tunables/global for contents.
24+
include <tunables/global>
25+
26+
# Require that the system understands the feature set that this policy was written
27+
# for. If we didn't include this, then on Ubuntu >= 22.04, AppArmor might assume
28+
# the wrong feature set was requested, and some rules might become too permissive.
29+
# See https://github.com/netblue30/firejail/issues/3659#issuecomment-711074899
30+
abi <abi/3.0>,
31+
32+
# This outer profile applies to the entire container, and isn't as
33+
# important as the inner (codejail_sandbox) profile. If the inner profile doesn't work, it's not likely that
34+
# the outer one is going to help. But there may be some small value in
35+
# defense-in-depth, as it's possible that a bug in the codejail_sandbox (inner)
36+
# profile isn't present in the outer one.
37+
profile codejail_service flags=(mediate_deleted) {
38+
39+
# Allow access to a variety of commonly needed, generally safe things
40+
# (such as reading /dev/random, free memory, etc.)
41+
#
42+
# Manpage: "Includes files that should be readable and writable in all profiles."
43+
include <abstractions/base>
44+
45+
# Filesystem access -- self-explanatory
46+
file,
47+
48+
# netlink is needed for sudo's interprocess communication
49+
network netlink raw,
50+
51+
# Allow all of the various network operations required to listen, accept connection, etc.
52+
network tcp,
53+
# But then deny making a new *outbound* connection.
54+
deny network (connect) tcp,
55+
56+
# Required for sudoing to sandbox
57+
capability setuid setgid audit_write,
58+
# Allow sending a kill signal
59+
capability kill,
60+
61+
# Allow sending a kill signal to the codejail_sandbox subprofile when the execution
62+
# runs beyond time limits.
63+
signal (send) set=(kill) peer=codejail_service//codejail_sandbox,
64+
65+
# The core of the confinement: When the sandbox Python is executed, switch to
66+
# the (extremely constrained) codejail_sandbox profile.
67+
#
68+
# This path needs to be coordinated with the Dockerfile and Django settings.
69+
#
70+
# Manpage: "Cx: transition to subprofile on execute -- scrub the environment"
71+
/sandbox/venv/bin/python Cx -> codejail_sandbox,
72+
73+
# This is the important apparmor profile -- the one that actually
74+
# constrains the sandbox Python process.
75+
#
76+
# mediate_deleted is not well documented, but it seems to indicate that
77+
# apparmor will continue to make policy decisions in cases where a confined
78+
# executable has a handle to a file's inode even after the file is removed
79+
# from the filesystem.
80+
profile codejail_sandbox flags=(mediate_deleted) {
81+
82+
# This inner profile also gets general access to "safe"
83+
# actions; we could list those explicitly out of caution but
84+
# it could get pretty verbose.
85+
include <abstractions/base>
86+
87+
# Read and run binaries and libraries in the virtualenv. This
88+
# includes the sandbox's copy of Python as well as any
89+
# dependencies that have been installed for inclusion in
90+
# sandboxes.
91+
#
92+
# m: executable mapping, required for shared libraries used by some
93+
# Python dependencies with C compontents, eg `nltk`.
94+
/sandbox/venv/** rm,
95+
96+
# Allow access to the temporary directories that are set up by
97+
# codejail, one for each code-exec call. Each /tmp/code-XXXXX
98+
# contains one execution.
99+
#
100+
# Codejail has a hardcoded reference to this file path, although the
101+
# use of /tmp specifically may be controllable with environment variables:
102+
# https://github.com/openedx/codejail/blob/0165d9ca351/codejail/util.py#L15
103+
/tmp/codejail-*/ r,
104+
/tmp/codejail-*/** rw,
105+
106+
# Allow interactive terminal in devstack.
107+
/dev/pts/* rw,
108+
109+
# Allow receiving a kill signal from the webapp when the execution
110+
# runs beyond time limits.
111+
signal (receive) set=(kill) peer=codejail_service,
112+
}
113+
}

Diff for: docker-compose.yml

+3-1
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"

Diff for: docs/codejail.rst

+43
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+
The `codejail-service <https://github.com/openedx/codejail-service>`__ webapp is a wrapper around the `codejail <https://github.com/openedx/codejail>`__ library. See the READMEs of each repo for more information on the special requirements for deploying codejail, in particular the AppArmor-based sandboxing.
10+
11+
References to "codejail" can mean either the library or the service. In devstack, "codejail" usually refers to the service.
12+
13+
Configuration
14+
*************
15+
16+
These instructions are for Linux only. Additional research would be required to create instructions for a Mac, which probably involves accessing the Linux VM that docker is run inside of.
17+
18+
In order to run the codejail devstack component:
19+
20+
1. Install AppArmor: ``sudo apt install apparmor``
21+
2. Add the provided codejail AppArmor profile to your OS: ``sudo apparmor_parser --add -W ./codejail.profile``
22+
3. Configure LMS and CMS to use the codejail-service by uncommenting ``# ENABLE_CODEJAIL_REST_SERVICE = 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+
Over time, the AppArmor profile may need to be updated. Changes to the file do not automatically cause changes to the version that has been installed in the OS. When significant changes have been made to the profile, you'll need to re-install the profile. This can be done by passing ``--replace`` instead of ``--add``, like so: ``sudo apparmor_parser --replace -W ./codejail.profile``
28+
29+
Development
30+
***********
31+
32+
Changes to the AppArmor profile must be coordinated with changes to the Dockerfile, as they need to agree on filesystem paths.
33+
34+
Any time you update the profile file, you'll need to update the profile in your OS as well: ``sudo apparmor_parser --replace -W ./codejail.profile``
35+
36+
The profile file contains the directive ``profile codejail_service``. That defines the name of the profile when it is installed into the OS, and must agree with the relevant ``security_opt`` line in ``docker-compose.yml``. This name should not be changed, as it creates a confusing situation and would require every developer who uses codejail-service to do a number of manual steps. (Profiles can't be renamed *within* the OS; they must first be removed **under the old name**, and then a new profile must be installed under the new name.)
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.

Diff for: docs/index.rst

+1
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

Diff for: py_configuration_files/cms.py

+10
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,16 @@ 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 = True
320+
321+
# Note that this is exposed on port 8080 to other devstack services,
322+
# but 18030 outside of Docker.
323+
CODE_JAIL_REST_SERVICE_HOST = "http://edx.devstack.codejail:8080"
324+
315325

316326
################# New settings must go ABOVE this line #################
317327
########################################################################

Diff for: py_configuration_files/codejail.py

+26
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,29 @@
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 (in edx/public-dockerfiles)
16+
# and the AppArmor profile (codejail.profile in edx/devstack).
17+
'python_bin': '/sandbox/venv/bin/python',
18+
'user': 'sandbox',
19+
20+
# Configurable limits.
21+
'limits': {
22+
# CPU-seconds
23+
'CPU': 3,
24+
# 100 MiB memory
25+
'VMEM': 100 * 1024 * 1024,
26+
# Clock seconds
27+
'REALTIME': 3,
28+
},
29+
}

Diff for: py_configuration_files/lms.py

+9
Original file line numberDiff line numberDiff line change
@@ -554,6 +554,15 @@ 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 = True
562+
563+
# Note that this is exposed on port 8080 to other devstack services,
564+
# but 18030 outside of Docker.
565+
CODE_JAIL_REST_SERVICE_HOST = "http://edx.devstack.codejail:8080"
557566

558567
################# New settings must go ABOVE this line #################
559568
########################################################################

0 commit comments

Comments
 (0)