From c9affbaf50505058617675f7a3000c17be47b9c6 Mon Sep 17 00:00:00 2001 From: David Wilson Date: Wed, 14 Feb 2018 09:16:27 +0545 Subject: [PATCH] core: add auth_id field. --- docs/howitworks.rst | 77 ++++++++++++++++++++++++++++++++++----------- mitogen/core.py | 24 +++++++------- 2 files changed, 71 insertions(+), 30 deletions(-) diff --git a/docs/howitworks.rst b/docs/howitworks.rst index 517ea688..a9dd503a 100644 --- a/docs/howitworks.rst +++ b/docs/howitworks.rst @@ -260,21 +260,55 @@ Stream Protocol Once connected, a basic framing protocol is used to communicate between parent and child: -+--------------------+------+------------------------------------------------------+ -| Field | Size | Description | -+====================+======+======================================================+ -| ``dst_id`` | 2 | Integer target context ID. | -+--------------------+------+------------------------------------------------------+ -| ``src_id`` | 2 | Integer source context ID. | -+--------------------+------+------------------------------------------------------+ -| ``handle`` | 4 | Integer target handle in recipient. | -+--------------------+------+------------------------------------------------------+ -| ``reply_to`` | 4 | Integer response target ID. | -+--------------------+------+------------------------------------------------------+ -| ``length`` | 4 | Message length | -+--------------------+------+------------------------------------------------------+ -| ``data`` | n/a | Pickled message data. | -+--------------------+------+------------------------------------------------------+ +.. list-table:: + :header-rows: 1 + :widths: auto + + * - Field + - Size + - Description + + * - `dst_id` + - 2 + - Integer target context ID. :py:class:`Router` delivers messages + locally when their `dst_id` matches :py:data:`mitogen.context_id`, + otherwise they are routed up or downstream. + + * - `src_id` + - 2 + - Integer source context ID. Used as the target of replies if any are + generated. + + * - `auth_id` + - 2 + - The context ID under whose authority the message is acting. See + :py:ref:`source-verification`. + + * - `handle` + - 4 + - Integer target handle in the destination context. This is one of the + :py:ref:`standard-handles`, or a dynamically generated handle used to + receive a one-time reply, such as the return value of a function call. + + * - `reply_to` + - 4 + - Integer target handle to direct any reply to this message. Used to + receive a one-time reply, such as the return value of a function call. + + * - `length` + - 4 + - Length of the data part of the message. + + * - `data` + - n/a + - Message data, which may be raw or pickled. + + + +.. _standard-handles: + +Standard Handles +################ Masters listen on the following handles: @@ -504,14 +538,21 @@ Source Verification Before forwarding or dispatching a message it has received, :py:class:`mitogen.core.Router` first looks up the corresponding :py:class:`mitogen.core.Stream` it would use to send responses towards the -message source, and if the looked up stream does not match the stream on which -the message was received, the message is discarded and a warning is logged. +context ID listed in the `auth_id` field, and if the looked up stream does not +match the stream on which the message was received, the message is discarded +and a warning is logged. This creates a trust chain leading up to the root of the tree, preventing downstream contexts from injecting messages appearing to be from the master or any more trustworthy parent. In this way, privileged functionality such as :py:data:`CALL_FUNCTION ` can base trust decisions -on the accuracy of :py:ref:`src_id `. +on the accuracy of :py:ref:`auth_id `. + +The `auth_id` field is separate from `src_id` in order to support granting +privilege to contexts that do not follow the tree's natural trust chain. This +supports cases where siblings are permitted to execute code on one another, or +where isolated processes can connect to a listener and communicate with an +already established established tree. Future diff --git a/mitogen/core.py b/mitogen/core.py index 1b716bab..17f4482b 100644 --- a/mitogen/core.py +++ b/mitogen/core.py @@ -217,6 +217,7 @@ def enable_profiling(): class Message(object): dst_id = None src_id = None + auth_id = None handle = None reply_to = None data = '' @@ -225,6 +226,7 @@ class Message(object): def __init__(self, **kwargs): self.src_id = mitogen.context_id + self.auth_id = mitogen.context_id vars(self).update(kwargs) def _unpickle_context(self, context_id, name): @@ -264,9 +266,9 @@ class Message(object): raise StreamError('invalid message: %s', ex) def __repr__(self): - return 'Message(%r, %r, %r, %r, %r..%d)' % ( - self.dst_id, self.src_id, self.handle, self.reply_to, - (self.data or '')[:50], len(self.data) + return 'Message(%r, %r, %r, %r, %r, %r..%d)' % ( + self.dst_id, self.src_id, self.auth_id, self.handle, + self.reply_to, (self.data or '')[:50], len(self.data) ) @@ -307,8 +309,6 @@ def _queue_interruptible_get(queue, timeout=None, block=True): if timeout is not None: timeout += time.time() - LOG.info('timeout = %r, block = %r', timeout, block) - msg = None while msg is None and (timeout is None or timeout > time.time()): try: @@ -407,6 +407,7 @@ class Importer(object): 'mitogen.compat.pkgutil', 'mitogen.fakessh', 'mitogen.master', + 'mitogen.parent', 'mitogen.ssh', 'mitogen.sudo', 'mitogen.utils', @@ -635,6 +636,7 @@ class Stream(BasicStream): protocol `. """ _input_buf = '' + auth_id = None def __init__(self, router, remote_id, **kwargs): self._router = router @@ -663,7 +665,7 @@ class Stream(BasicStream): if not buf: return self.on_disconnect(broker) - HEADER_FMT = '>hhLLL' + HEADER_FMT = '>hhhLLL' HEADER_LEN = struct.calcsize(HEADER_FMT) def _receive_one(self, broker): @@ -674,7 +676,7 @@ class Stream(BasicStream): # To support unpickling Contexts. msg.router = self._router - (msg.dst_id, msg.src_id, + (msg.dst_id, msg.src_id, msg.auth_id, msg.handle, msg.reply_to, msg_len) = struct.unpack( self.HEADER_FMT, self._input_buf[:self.HEADER_LEN] @@ -711,7 +713,7 @@ class Stream(BasicStream): def _send(self, msg): IOLOG.debug('%r._send(%r)', self, msg) - pkt = struct.pack('>hhLLL', msg.dst_id, msg.src_id, + pkt = struct.pack('>hhhLLL', msg.dst_id, msg.src_id, msg.auth_id, msg.handle, msg.reply_to or 0, len(msg.data) ) + msg.data self._output_buf.append(pkt) @@ -765,8 +767,6 @@ class Context(object): """send `obj` to `handle`, and tell the broker we have output. May be called from any thread.""" msg.dst_id = self.context_id - if msg.src_id is None: - msg.src_id = mitogen.context_id self.router.route(msg) def send_async(self, msg, persist=False): @@ -979,10 +979,10 @@ class Router(object): IOLOG.debug('%r._async_route(%r, %r)', self, msg, stream) # Perform source verification. if stream is not None: - expected_stream = self._stream_by_id.get(msg.src_id, + expected_stream = self._stream_by_id.get(msg.auth_id, self._stream_by_id.get(mitogen.parent_id)) if stream != expected_stream: - LOG.error('%r: bad source: got %r from %r, should be from %r', + LOG.error('%r: bad source: got auth ID %r from %r, should be from %r', self, msg, stream, expected_stream) if msg.dst_id == mitogen.context_id: