Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 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
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.*.swp
!.gitignore
TODO
__pycache__
*.egg-info/
/build/
/dist/
2 changes: 2 additions & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
recursive-include tutorcodejail/patches *
recursive-include tutorcodejail/templates *
61 changes: 61 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import io
import os
from setuptools import setup, find_packages

HERE = os.path.abspath(os.path.dirname(__file__))


def load_readme():
with io.open(os.path.join(HERE, "README.rst"), "rt", encoding="utf8") as f:
return f.read()


def load_about():
about = {}
with io.open(
os.path.join(HERE, "tutorcodejail", "__about__.py"),
"rt",
encoding="utf-8",
) as f:
exec(f.read(), about) # pylint: disable=exec-used
return about


ABOUT = load_about()


setup(
name="tutor-contrib-codejail",
version=ABOUT["__version__"],
url="https://github.com/github/tutor-contrib-codejail",
project_urls={
"Code": "https://github.com/edunext/tutor-contrib-codejail",
"Issue tracker": "https://github.com/edunext/tutor-contrib-codejail/issues",
},
license="AGPLv3",
author="Eric Herrera",
description="codejail plugin for Tutor",
long_description=load_readme(),
packages=find_packages(exclude=["tests*"]),
include_package_data=True,
python_requires=">=3.5",
install_requires=["tutor-openedx"],
entry_points={
"tutor.plugin.v0": [
"codejail = tutorcodejail.plugin"
]
},
classifiers=[
"Development Status :: 3 - Alpha",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU Affero General Public License v3",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3.5",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
],
)
1 change: 1 addition & 0 deletions tutorcodejail/__about__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__version__ = "0.1.0"
Empty file added tutorcodejail/__init__.py
Empty file.
Empty file.
1 change: 1 addition & 0 deletions tutorcodejail/patches/common-env-features
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"ENABLE_CODEJAIL_REST_SERVICE": true
1 change: 1 addition & 0 deletions tutorcodejail/patches/lms-env
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
"CODE_JAIL_REST_SERVICE_HOST": "http://{{ CODEJAIL_HOST }}:8550"
4 changes: 4 additions & 0 deletions tutorcodejail/patches/local-docker-compose-dev-services
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
codejailservice:
command: ./manage.py runserver 0.0.0.0:8550
ports:
- "8550:8550"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

add: restart: unless-stopped.

7 changes: 7 additions & 0 deletions tutorcodejail/patches/local-docker-compose-jobs-services
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
codejail_apparmor-job:
image: ednxops/codejail_apparmor_loader:latest
privileged: true
volumes:
- ../plugins/codejail/apps/profiles/docker-edx-sandbox:/profiles/docker-edx-sandbox:ro
- /sys:/sys
- /etc/apparmor.d:/etc/apparmor.d
11 changes: 11 additions & 0 deletions tutorcodejail/patches/local-docker-compose-services
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#############Codejail service
codejailservice:
image: {{ CODEJAIL_DOCKER_IMAGE }}
environment:
DJANGO_SETTINGS_MODULE: codejailservice.settings.tutor
security_opt:
- apparmor:docker-edx-sandbox
volumes:
- ../plugins/codejail/apps/settings/tutor.py:/openedx/codejailservice/codejailservice/settings/tutor.py:ro
- ../../data/codejail:/openedx/data
restart: unless-stopped
45 changes: 45 additions & 0 deletions tutorcodejail/plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from glob import glob
import os
import pkg_resources

from .__about__ import __version__

templates = pkg_resources.resource_filename(
"tutorcodejail", "templates"
)

config = {
"add": {
"SECRET_KEY": "{{ 24|random_string }}",
},
"defaults": {
"VERSION": __version__,
"HOST": "codejailservice.{{ LMS_HOST }}",
"DOCKER_IMAGE": "docker.io/ednxops/codejailservice:latest",
}
}

hooks = {
"build-image": {
"codejail": "{{ CODEJAIL_DOCKER_IMAGE }}",
"codejail_apparmor": "docker.io/ednxops/codejail_apparmor:latest"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I suggest to replace ":latest" by the actual plugin version. This guarantees that the Docker image is always pinned. This is why I always add a "VERSION" setting. See for instance: https://github.com/overhangio/tutor-mfe/blob/932f2db9fdf4c9370abeec5e89a558d9f590f133/tutormfe/plugin.py#L13

},
"remote-image": {
"codejail": "{{ CODEJAIL_DOCKER_IMAGE }}",
"codejail_apparmor": "docker.io/ednxops/codejail_apparmor:latest"
},
"init": ["codejail_apparmor"]
}


def patches():
all_patches = {}
patches_dir = pkg_resources.resource_filename(
"tutorcodejail", "patches"
)
for path in glob(os.path.join(patches_dir, "*")):
with open(path) as patch_file:
name = os.path.basename(path)
content = patch_file.read()
all_patches[name] = content
return all_patches
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
#include <tunables/global>

profile docker-edx-sandbox flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>

network,
capability,
file,
umount,
signal (receive) peer=unconfined,
signal (receive) peer=cri-containerd.apparmor.d,
signal (send,receive) peer=docker-edx-sandbox,

deny @{PROC}/* w, # deny write for all files directly in /proc (not in a subdir)
# deny write to files not in /proc/<number>/** or /proc/sys/**
deny @{PROC}/{[^1-9],[^1-9][^0-9],[^1-9s][^0-9y][^0-9s],[^1-9][^0-9][^0-9][^0-9]*}/** w,
deny @{PROC}/sys/[^k]** w, # deny /proc/sys except /proc/sys/k* (effectively /proc/sys/kernel)
deny @{PROC}/sys/kernel/{?,??,[^s][^h][^m]**} w, # deny everything except shm* in /proc/sys/kernel/
deny @{PROC}/sysrq-trigger rwklx,
deny @{PROC}/mem rwklx,
deny @{PROC}/kmem rwklx,
deny @{PROC}/kcore rwklx,

deny mount,
deny /sys/[^f]*/** wklx,
deny /sys/f[^s]*/** wklx,
deny /sys/fs/[^c]*/** wklx,
deny /sys/fs/c[^g]*/** wklx,
deny /sys/fs/cg[^r]*/** wklx,
deny /sys/firmware/** rwklx,
deny /sys/kernel/security/** rwklx,

ptrace (trace,read) peer=docker-edx-sandbox,

/sandbox/venv/bin/python Cx -> child,
profile child flags=(attach_disconnected,mediate_deleted){
#include <abstractions/base>
#include <abstractions/python>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This will only work if the same python version is installed on the host as in the container. I'm afraid we need to copy-paste the contents of this file into this template.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@regisb This is the content of that file in my host:

  /usr/lib{,32,64}/python{2.[4-7],3.[0-9]}/**.{pyc,so}           mr,
  /usr/lib{,32,64}/python{2.[4-7],3.[0-9]}/**.{egg,py,pth}       r,
  /usr/lib{,32,64}/python{2.[4-7],3.[0-9]}/{site,dist}-packages/ r,
  /usr/lib{,32,64}/python3.[0-9]/lib-dynload/*.so            mr,

  /usr/local/lib{,32,64}/python{2.[4-7],3,3.[0-9]}/**.{pyc,so}           mr,
  /usr/local/lib{,32,64}/python{2.[4-7],3,3.[0-9]}/**.{egg,py,pth}       r,
  /usr/local/lib{,32,64}/python{2.[4-7],3,3.[0-9]}/{site,dist}-packages/ r,
  /usr/local/lib{,32,64}/python3.[0-9]/lib-dynload/*.so            mr,

  # Site-wide configuration
  /etc/python{2.[4-7],3.[0-9]}/** r,

  # shared python paths
  /usr/share/{pyshared,pycentral,python-support}/**      r,
  /{var,usr}/lib/{pyshared,pycentral,python-support}/**  r,
  /usr/lib/{pyshared,pycentral,python-support}/**.so     mr,
  /var/lib/{pyshared,pycentral,python-support}/**.pyc    mr,
  /usr/lib/python3/dist-packages/**.so          mr,

  # wx paths
  /usr/lib/wx/python/*.pth r,

  # python build configuration and headers
  /usr/include/python{2.[4-7],3.[0-9]}*/pyconfig.h r,

I checked those paths in the codejailservice container and any of them actually exists, I think it is because I am using pyenv. Can we just then get rid of this ?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The thing is that you cannot add #include <abstractions/python> to "docker-edx-sandbox" because you do not know which Python versions are installed on the host, and whether that file is even there. On my Ubuntu 20.04 computer, the /etc/apparmor.d/abstractions/python file is shipped with the apparmor Debian package and the library path regular expressions match Python version from 2.4 to 3.9. But on my Debian 4.9 server the /etc/apparmor.d/abstractions/python file is not even there. If it were there, it might not include support for Python 3.8.

So you need to copy-paste the contents of this file and to change the paths such that it matches the paths for the pyenv-installed python in the Docker container.


#
# Whitelist particiclar shared objects from the system
# python installation
#
/sandbox/venv/** mr,
/opt/pyenv/versions/3.5.10/** mr,
/tmp/codejail-*/ rix,
/tmp/codejail-*/** wrix,

#
# Whitelist particular shared objects from the system
# python installation
#
/sandbox/venv/.config/ wrix,
/sandbox/venv/.cache/ wrix,
/sandbox/venv/.config/** wrix,
/sandbox/venv/.cache/** wrix,

# Matplot lib needs fonts to make graphs
/usr/share/fonts/ r,
/usr/share/fonts/** r,
/usr/local/share/fonts/ r,
/usr/local/share/fonts/** r,

#
# Allow access to selections from /proc
#
/proc/*/mounts r,
}
}
41 changes: 41 additions & 0 deletions tutorcodejail/templates/codejail/apps/settings/tutor.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from .base import *

from codejail.django_integration_utils import apply_django_settings

SECRET_KEY = "{{ CODEJAIL_SECRET_KEY }}"
ALLOWED_HOSTS = [
"{{ CODEJAIL_HOST }}",
]

#################### Python sandbox ############################################

CODE_JAIL = {
'python_bin': '/sandbox/venv/bin/python',
# User to run as in the sandbox.
'user': 'sandbox',

# Configurable limits.
# Setting all of them to 0 to disable limits in conatiners.
'limits': {
#
'NPROC': 0,
# How many CPU seconds can jailed code use?
'CPU': 0,
# Limit the memory of the jailed process to something high but not
# infinite (512MiB in bytes)
'VMEM': 0,
# Time in seconds that the jailed process has to run.
'REALTIME': 0,
# Needs to be non-zero so that jailed code can use it as their temp directory.(10MiB in bytes)
'FSIZE': 10485760,
# Disable usage of proxy (force thread-safe)
'PROXY': 0,
},

# Overrides to default configurable 'limits' (above).
# Keys should be course run ids.
# Values should be dictionaries that look like 'limits'.
"limit_overrides": {},
}

apply_django_settings(CODE_JAIL)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Unless I'm mistaken, creating a Django project would require defining a database. A full Django project seems overkill for what you are trying to achieve here, meaning a stateless python interpreter with a very basic API. Can I suggest relying on a more lightweight framework instead, such as Flask?

Empty file.
78 changes: 78 additions & 0 deletions tutorcodejail/templates/codejail/build/codejail/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
FROM docker.io/ubuntu:20.04 as minimal
MAINTAINER Overhang.io <contact@overhang.io>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

You should change this if you intend to support this image :)


ENV DEBIAN_FRONTEND=noninteractive
RUN apt update && \
apt install -y build-essential curl git language-pack-en
ENV LC_ALL en_US.UTF-8

###### Install python with pyenv in /opt/pyenv and create virtualenv in /openedx/venv
FROM minimal as python
# https://github.com/pyenv/pyenv/wiki/Common-build-problems#prerequisites
RUN apt update && \
apt install -y libssl-dev zlib1g-dev libbz2-dev \
libreadline-dev libsqlite3-dev wget curl llvm libncurses5-dev libncursesw5-dev \
xz-utils tk-dev libffi-dev liblzma-dev python-openssl git subversion
ENV PYENV_ROOT /opt/pyenv
RUN git clone https://github.com/pyenv/pyenv $PYENV_ROOT --branch v1.2.21 --depth 1

ARG CODEJAILSERVICE_PYTHON_VERSION=3.8.6
RUN $PYENV_ROOT/bin/pyenv install $CODEJAILSERVICE_PYTHON_VERSION

ARG SANDBOX_PYTHON_VERSION=3.5.10
RUN $PYENV_ROOT/bin/pyenv install $SANDBOX_PYTHON_VERSION

@regisb regisb Sep 9, 2021 •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

In the future, I think that all code below this line should be run as a different user (e.g: USER openedx). This is important for people running Docker containers in unprivileged environments (as is often the case in Kubernetes). It's not a deal-breaker for this first release, but you should this in mind for the future.

RUN $PYENV_ROOT/versions/$CODEJAILSERVICE_PYTHON_VERSION/bin/python -m venv /openedx/venv
RUN $PYENV_ROOT/versions/$SANDBOX_PYTHON_VERSION/bin/python -m venv --copies /sandbox/venv

###### Codejail service code
FROM minimal as code
RUN git clone https://github.com/eduNEXT/codejailservice.git --branch eric/mvp --depth 1 /openedx/codejailservice
WORKDIR /openedx/codejailservice

###### Install python requirements in virtualenv
FROM python as codejailservice-python-requirements

ENV PATH /openedx/venv/bin:${PATH}
ENV VIRTUAL_ENV /openedx/venv/

COPY --from=code /openedx/codejailservice /openedx/codejailservice
WORKDIR /openedx/codejailservice
RUN pip3 install -r requirements/base.txt
RUN pip3 install ipdb

###### Install python requirements in virtualenv
FROM python as sandbox-python-requirements

ENV PATH /sandbox/venv/bin:${PATH}
ENV VIRTUAL_ENV /sandbox/venv/

WORKDIR /var/tmp
RUN mkdir -p common/lib/
RUN svn export https://github.com/edx/edx-platform.git/tags/open-release/koa.3/common/lib/sandbox-packages common/lib/sandbox-packages
RUN svn export https://github.com/edx/edx-platform.git/tags/open-release/koa.3/common/lib/symmath common/lib/symmath

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

What does svn export do? Do we really need subversion or can we get rid of it? If we just to copy a file from edx-platform, it would be safer to COPY it from the docker image labeled DOCKER_IMAGE_OPENEDX.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

You are right, it is a good idea to copy it from the edx-platform image, I am going to try that.

# RUN wget https://raw.githubusercontent.com/edx/edx-platform/open-release/koa.3/requirements/edx-sandbox/base.txt && pip3 install base.txt
RUN wget https://raw.githubusercontent.com/edx/edx-platform/open-release/koa.3/requirements/edx-sandbox/py35.txt && pip3 install -r py35.txt

##### Prod image
FROM minimal as production

# Install system requirements
RUN apt update && \
apt install -y sudo

COPY --from=code /openedx/codejailservice /openedx/codejailservice
COPY --from=python /opt/pyenv /opt/pyenv
COPY --from=codejailservice-python-requirements /openedx/venv /openedx/venv
COPY --from=sandbox-python-requirements /sandbox/venv /sandbox/venv

# Setup sandbox
ENV SANDBOX_ENV=/sandbox/venv
RUN groupadd -r sandbox && useradd -m -r -g sandbox sandbox && chown -R sandbox:sandbox /sandbox

ENV PATH /openedx/venv/bin:${PATH}
ENV VIRTUAL_ENV /openedx/venv/
WORKDIR /openedx/codejailservice

EXPOSE 8000
CMD gunicorn --workers=2 --name codejailservice --bind=0.0.0.0:8000 --max-requests=1000 codejailservice.wsgi:application

@ericfab179 ericfab179 Jul 22, 2021 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@NeOneSoft please change here to use uwsgi instead of gunicorn

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM golang:latest as go_compiler

RUN mkdir /app
WORKDIR /app
ADD https://raw.githubusercontent.com/kubernetes/kubernetes/master/test/images/apparmor-loader/loader.go loader.go
RUN go mod init loader
RUN go get k8s.io/klog/v2
RUN GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -a -installsuffix cgo --ldflags '-w' -o loader .

FROM alpine:latest

RUN apk add apparmor libapparmor --update-cache --repository http://dl-cdn.alpinelinux.org/alpine/edge/testing/ --allow-untrusted && \
apk add --no-cache musl\>1.1.20 --repository http://dl-cdn.alpinelinux.org/alpine/edge/main/

COPY --from=go_compiler /app/loader /usr/bin/loader
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/usr/bin/loader -logtostderr -v=2 /profiles