diff options
| -rw-r--r-- | config.py | 5 | ||||
| -rw-r--r-- | rmq.py | 11 | ||||
| -rw-r--r-- | snekbox.py | 28 | ||||
| -rw-r--r-- | snekweb.py | 2 | 
4 files changed, 33 insertions, 13 deletions
@@ -1,10 +1,10 @@  import os  import docker  from docker.errors import NotFound -# import traceback  def autodiscover(): +    """Search for the snekbox container and return it's IPv4 address."""      container_names = ["rmq", "pdrmq", "snekbox_pdrmq_1"]      client = docker.from_env() @@ -15,13 +15,10 @@ def autodiscover():                  host = list(container.attrs.get('NetworkSettings').get('Networks').values())                  host = host[0]['IPAddress']                  return host -          except NotFound:              continue -          except Exception:              pass -            # print(traceback.format_exc())      return '127.0.0.1' @@ -16,7 +16,8 @@ from config import EXCHANGE  from logs import log -class Rmq(object): +class Rmq: +    """Rabbit MQ (RMQ) implementation used for communication with the bot."""      def __init__(self,                   username=USERNAME, @@ -42,6 +43,7 @@ class Rmq(object):              auto_delete=True)                    # Delete queue when all connection are closed      def consume(self, queue=QUEUE, callback=None, thread_ws=None, run_once=False): +        """Subscribe to read from a RMQ channel."""          while True:              try:                  connection = pika.BlockingConnection(self.con_params) @@ -86,11 +88,8 @@ class Rmq(object):              time.sleep(2) -    def publish(self, -                message, -                queue=QUEUE, -                routingkey=ROUTING_KEY, -                exchange=EXCHANGE): +    def publish(self, message, queue=QUEUE, routingkey=ROUTING_KEY, exchange=EXCHANGE): +        """Open a connection to publish (write) to a RMQ channel."""          try:              connection = pika.BlockingConnection(self.con_params) @@ -7,14 +7,16 @@ import sys  from rmq import Rmq -class Snekbox(object): +class Snekbox: +    """Core snekbox functionality, providing safe execution of Python code.""" +      def __init__(self,                   nsjail_binary='nsjail',                   python_binary=os.path.dirname(sys.executable)+os.sep+'python3.6'):          self.nsjail_binary = nsjail_binary          self.python_binary = python_binary -        self.nsjail_workaround() +        self._nsjail_workaround()      env = {          'PATH': ( @@ -27,13 +29,23 @@ class Snekbox(object):          'PYTHONDONTWRITEBYTECODE': '1',      } -    def nsjail_workaround(self): +    def _nsjail_workaround(self):          dirs = ['/sys/fs/cgroup/pids/NSJAIL', '/sys/fs/cgroup/memory/NSJAIL']          for d in dirs:              if not os.path.exists(d):                  os.makedirs(d)      def python3(self, cmd): +        """ +        Execute Python 3 code in a isolated environment. + +        The value of ``cmd`` is passed using '-c' to a Python +        interpreter that is started in a ``nsjail``, isolating it +        from the rest of the system. + +        Returns the output of executing the command (stdout) if +        successful, or a error message if the execution failed. +        """          args = [self.nsjail_binary, '-Mo',                  '--rlimit_as', '700',                  '--chroot', '/', @@ -87,6 +99,15 @@ class Snekbox(object):          return output      def execute(self, body): +        """ +        Handles execution of a raw JSON-formatted RMQ message, contained in ``body``. + +        The message metadata, including the Python code to be executed, is +        extracted from the message body. The code is then executed in the +        isolated environment, and the results of the execution published +        to RMQ. Once published, the system exits, since the snekboxes +        are created and disposed of per-execution. +        """          msg = body.decode('utf-8')          result = ''          snek_msg = json.loads(msg) @@ -102,6 +123,7 @@ class Snekbox(object):          exit(0)      def message_handler(self, ch, method, properties, body, thread_ws=None): +        """Spawns a daemon process that handles RMQ messages."""          p = multiprocessing.Process(target=self.execute, args=(body,))          p.daemon = True          p.start() @@ -24,11 +24,13 @@ log = app.logger  @app.route('/')  def index(): +    """Root path returns standard index.html."""      return render_template('index.html')  @sockets.route('/ws/<snekboxid>')  def websocket_route(ws, snekboxid): +    """Opens a websocket that spawns and connects to a snekbox daemon."""      localdata = threading.local()      localdata.thread_ws = ws  |